def get_data(self) -> pd.DataFrame: from eegnb.devices.utils import create_stim_array data = self.board.get_board_data() # will clear board buffer # transform data for saving data = data.T # transpose data print(data) # get the channel names for EEG data if self.brainflow_id == BoardIds.GANGLION_BOARD.value: # if a ganglion is used, use recommended default EEG channel names ch_names = ["fp1", "fp2", "tp7", "tp8"] else: # otherwise select eeg channel names via brainflow API ch_names = BoardShim.get_eeg_names(self.brainflow_id) # pull EEG channel data via brainflow API eeg_data = data[:, BoardShim.get_eeg_channels(self.brainflow_id)] timestamps = data[:, BoardShim.get_timestamp_channel(self.brainflow_id)] # Create a column for the stimuli to append to the EEG data stim_array = create_stim_array(timestamps, self.markers) timestamps = timestamps[ ..., None] # Add an additional dimension so that shapes match total_data = np.append(timestamps, eeg_data, 1) total_data = np.append(total_data, stim_array, 1) # Append the stim array to data. # Subtract five seconds of settling time from beginning # total_data = total_data[5 * self.sfreq :] df = pd.DataFrame(total_data, columns=["timestamps"] + ch_names + ["stim"]) return df
def _stop_brainflow(self): """This functions kills the brainflow backend and saves the data to a CSV file.""" # Collect session data and kill session data = self.board.get_board_data() # will clear board buffer self.board.stop_stream() self.board.release_session() # transform data for saving data = data.T # transpose data # get the channel names for EEG data if self.brainflow_id == BoardIds.GANGLION_BOARD.value: # if a ganglion is used, use recommended default EEG channel names ch_names = ['fp1', 'fp2', 'tp7', 'tp8'] else: # otherwise select eeg channel names via brainflow API ch_names = BoardShim.get_eeg_names(self.brainflow_id) # pull EEG channel data via brainflow API eeg_data = data[:, BoardShim.get_eeg_channels(self.brainflow_id)] timestamps = data[:, BoardShim.get_timestamp_channel(self.brainflow_id)] # Create a column for the stimuli to append to the EEG data stim_array = create_stim_array(timestamps, self.markers) timestamps = timestamps[..., None] # Add an additional dimension so that shapes match total_data = np.append(timestamps, eeg_data, 1) total_data = np.append(total_data, stim_array, 1) # Append the stim array to data. # Subtract five seconds of settling time from beginning total_data = total_data[5 * self.sfreq:] data_df = pd.DataFrame(total_data, columns=['timestamps'] + ch_names + ['stim']) data_df.to_csv(self.save_fn, index=False)
def _brainflow_extract(self, data): """ Formats the data returned from brainflow to get ch_names; list of channel names eeg_data: NDArray of eeg samples timestamps: NDArray of timestamps """ # transform data for saving data = data.T # transpose data # get the channel names for EEG data if (self.brainflow_id == BoardIds.GANGLION_BOARD.value or self.brainflow_id == BoardIds.GANGLION_WIFI_BOARD.value): # if a ganglion is used, use recommended default EEG channel names ch_names = ["fp1", "fp2", "tp7", "tp8"] elif self.brainflow_id == BoardIds.FREEEEG32_BOARD.value: ch_names = [f"eeg_{i}" for i in range(0, 32)] else: # otherwise select eeg channel names via brainflow API ch_names = BoardShim.get_eeg_names(self.brainflow_id) # pull EEG channel data via brainflow API eeg_data = data[:, BoardShim.get_eeg_channels(self.brainflow_id)] timestamps = data[:, BoardShim.get_timestamp_channel(self.brainflow_id)] return ch_names, eeg_data, timestamps
def initialize_eeg(self, board_type='synthetic', connection_method='usb', usb_port=None): BoardShim.enable_dev_board_logger() self.board_id, self.params = get_board_info(board_type, connection_method, usb_port) self.board = BoardShim(self.board_id, self.params) self.board.prepare_session()
def initialize_eeg(self, board_type='synthetic', usb_port=None, ip_addr=None, ip_port=None, serial_num=None): BoardShim.enable_dev_board_logger() self.board_id, self.params = get_board_info(board_type, usb_port, ip_addr, ip_port, serial_num) self.board = BoardShim(self.board_id, self.params) self.board.prepare_session()
def __init__(self, board_id=BoardIds.CYTON_DAISY_BOARD.value, ip_port=6677, serial_port="COM3"): # Board params self.board_id = board_id self.params = BrainFlowInputParams() self.params.ip_port = ip_port self.params.serial_port = serial_port self.board = BoardShim(board_id, self.params) self.sfreq = self.board.get_sampling_rate(board_id) self.marker_row = self.board.get_marker_channel(self.board_id) self.eeg_names = self.board.get_eeg_names(board_id) # Features params # todo: get as arg self.features_params = {'channels': ['C03', 'C04']}
def acquire_signals(): count = 0 while True: with mutex: # print("acquisition_phase") if count == 0: time.sleep(2) count += 1 # else: time.sleep(0.5) # get_current_board_data does not remove personal_dataset from board internal buffer # thus allowing us to acquire overlapped personal_dataset and compute more classification over 1 sec data = board.get_current_board_data(250) sample = [] eeg_channels = BoardShim.get_eeg_channels(BoardIds.CYTON_BOARD.value) for channel in eeg_channels: sample.append(data[channel]) shared_vars.sample = np.array(sample) # print(shared_vars.sample.shape) if shared_vars.key == ord("q"): break # print("sample_acquired") time.sleep(0.1)
def sample_to_second(sample: int, board_id: int): """" This function takes a sample number and a board id. It returns the seconds that have passed from sample 0 to the given sample, based on the sampling frequency of the given board. """ return sample / BoardShim.get_sampling_rate(board_id)
def initialize_backend(self): if self.backend == "brainflow": self._init_brainflow() self.timestamp_channel = BoardShim.get_timestamp_channel( self.brainflow_id) elif self.backend == "muselsl": self._init_muselsl() self._muse_get_recent() # run this at initialization to get some
def check(self, max_uv_abs=200) -> List[str]: data = self.board.get_board_data() # will clear board buffer # print(data) channel_names = BoardShim.get_eeg_names(self.brainflow_id) # FIXME: _check_samples expects different (Muse) inputs checked = _check_samples(data.T, channel_names, max_uv_abs=max_uv_abs) # type: ignore bads = [ch for ch, ok in checked.items() if not ok] return bads
class freeRecording: def __init__(self, activity=None): self.board_prepared = False self.board_id = None self.params = None self.board = None self._setup_session(activity) def initialize_eeg(self, board_type='synthetic', usb_port=None, ip_addr=None, ip_port=None, serial_num=None): self.board_id, self.params = get_board_info(board_type, usb_port, ip_addr, ip_port, serial_num) self.board = BoardShim(self.board_id, self.params) self.board.prepare_session() self.board_prepared = True def _setup_session(self, activity): if activity == None: activity = 'UNLABELLED' self.session_name = activity def record(self, duration, subject, run): if self.board_prepared == False: self.board.prepare_session() self.board_prepared = True print( "Beginning EEG Stream; Wait 5 seconds for signal to settle... \n") self.board.start_stream() sleep(5) print(f"Starting recording for {duration} seconds... \n") sleep(duration) # cleanup the session self.board.stop_stream() # self.board_prepared = False data = self.board.get_board_data() data_fn, event_fn = get_fns(subject, run, self.session_name) DataFilter.write_file(data, data_fn, 'w')
def __init__(self, board_id: int = BoardIds.CYTON_DAISY_BOARD.value, ip_port: int = 6677, serial_port: Optional[str] = None, headset: str = "avi13"): # Board Id and Headset Name self.board_id = board_id self.headset: str = headset # BrainFlowInputParams self.params = BrainFlowInputParams() self.params.ip_port = ip_port self.params.serial_port = serial_port if serial_port is not None else self.find_serial_port( ) self.params.headset = headset self.params.board_id = board_id self.board = BoardShim(board_id, self.params) # Other Params self.sfreq = self.board.get_sampling_rate(board_id) self.marker_row = self.board.get_marker_channel(self.board_id) self.eeg_names = self.get_board_names()
def extract_eeg_data(raw_data, board_id: int) -> np.ndarray: """ Given a board id and a 2d array containing retrieved data from board, this method extracts only the eeg data into a separate 2d array containing eeg channels as rows and samples as columns. """ eeg_indexes = BoardShim.get_eeg_channels(board_id) eeg_channels = np.empty(shape=(len(eeg_indexes), len(raw_data[eeg_indexes[0]])), dtype=float) eeg_channel_index = 0 for row in range(len(raw_data)): if row in eeg_indexes: eeg_channels[eeg_channel_index] = raw_data[row] eeg_channel_index += 1 return eeg_channels
class EEG: def __init__( self, device=None, serial_port=None, serial_num=None, mac_addr=None, other=None, ip_addr=None, ): """The initialization function takes the name of the EEG device and determines whether or not the device belongs to the Muse or Brainflow families and initializes the appropriate backend. Parameters: device (str): name of eeg device used for reading data. """ # determine if board uses brainflow or muselsl backend self.device_name = device self.serial_num = serial_num self.serial_port = serial_port self.mac_address = mac_addr self.ip_addr = ip_addr self.other = other self.backend = self._get_backend(self.device_name) self.initialize_backend() def initialize_backend(self): if self.backend == "brainflow": self._init_brainflow() elif self.backend == "muselsl": self._init_muselsl() def _get_backend(self, device_name): if device_name in brainflow_devices: return "brainflow" elif device_name in ["muse2016", "muse2", "museS"]: return "muselsl" ##################### # MUSE functions # ##################### def _init_muselsl(self): # Currently there's nothing we need to do here. However keeping the # option open to add things with this init method. pass def _start_muse(self, duration): if sys.platform in ["linux", "linux2", "darwin"]: # Look for muses self.muses = list_muses() # self.muse = muses[0] # Start streaming process self.stream_process = Process(target=stream, args=(self.muses[0]["address"], )) self.stream_process.start() # Create markers stream outlet self.muse_StreamInfo = StreamInfo("Markers", "Markers", 1, 0, "int32", "myuidw43536") self.muse_StreamOutlet = StreamOutlet(self.muse_StreamInfo) # Start a background process that will stream data from the first available Muse print("starting background recording process") print("will save to file: %s" % self.save_fn) self.recording = Process(target=record, args=(duration, self.save_fn)) self.recording.start() time.sleep(5) self.push_sample([99], timestamp=time.time()) def _stop_muse(self): pass def _muse_push_sample(self, marker, timestamp): self.muse_StreamOutlet.push_sample(marker, timestamp) ########################## # BrainFlow functions # ########################## def _init_brainflow(self): """This function initializes the brainflow backend based on the input device name. It calls a utility function to determine the appropriate USB port to use based on the current operating system. Additionally, the system allows for passing a serial number in the case that they want to use either the BraintBit or the Unicorn EEG devices from the brainflow family. Parameters: serial_num (str or int): serial number for either the BrainBit or Unicorn devices. """ # Initialize brainflow parameters self.brainflow_params = BrainFlowInputParams() if self.device_name == "ganglion": self.brainflow_id = BoardIds.GANGLION_BOARD.value if self.serial_port == None: self.brainflow_params.serial_port = get_openbci_usb() # set mac address parameter in case if self.mac_address is None: print( "No MAC address provided, attempting to connect without one" ) else: self.brainflow_params.mac_address = self.mac_address elif self.device_name == "ganglion_wifi": self.brainflow_id = BoardIds.GANGLION_WIFI_BOARD.value if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr self.brainflow_params.ip_port = 6677 elif self.device_name == "cyton": self.brainflow_id = BoardIds.CYTON_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "cyton_wifi": self.brainflow_id = BoardIds.CYTON_WIFI_BOARD.value if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr self.brainflow_params.ip_port = 6677 elif self.device_name == "cyton_daisy": self.brainflow_id = BoardIds.CYTON_DAISY_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "cyton_daisy_wifi": self.brainflow_id = BoardIds.CYTON_DAISY_WIFI_BOARD.value if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr elif self.device_name == "brainbit": self.brainflow_id = BoardIds.BRAINBIT_BOARD.value elif self.device_name == "unicorn": self.brainflow_id = BoardIds.UNICORN_BOARD.value elif self.device_name == "callibri_eeg": self.brainflow_id = BoardIds.CALLIBRI_EEG_BOARD.value if self.other: self.brainflow_params.other_info = str(self.other) elif self.device_name == "notion1": self.brainflow_id = BoardIds.NOTION_1_BOARD.value elif self.device_name == "notion2": self.brainflow_id = BoardIds.NOTION_2_BOARD.value elif self.device_name == "freeeeg32": self.brainflow_id = BoardIds.FREEEEG32_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "synthetic": self.brainflow_id = BoardIds.SYNTHETIC_BOARD.value # some devices allow for an optional serial number parameter for better connection if self.serial_num: serial_num = str(self.serial_num) self.brainflow_params.serial_number = serial_num if self.serial_port: serial_port = str(self.serial_port) self.brainflow_params.serial_port = serial_port # Initialize board_shim self.sfreq = BoardShim.get_sampling_rate(self.brainflow_id) self.board = BoardShim(self.brainflow_id, self.brainflow_params) self.board.prepare_session() def _start_brainflow(self): self.board.start_stream() # wait for signal to settle sleep(5) def _stop_brainflow(self): """This functions kills the brainflow backend and saves the data to a CSV file.""" # Collect session data and kill session data = self.board.get_board_data() # will clear board buffer self.board.stop_stream() self.board.release_session() # transform data for saving data = data.T # transpose data # get the channel names for EEG data if (self.brainflow_id == BoardIds.GANGLION_BOARD.value or self.brainflow_id == BoardIds.GANGLION_WIFI_BOARD.value): # if a ganglion is used, use recommended default EEG channel names ch_names = ["fp1", "fp2", "tp7", "tp8"] elif (self.brainflow_id == BoardIds.FREEEEG32_BOARD.value): ch_names = [f'eeg_{i}' for i in range(0, 32)] else: # otherwise select eeg channel names via brainflow API ch_names = BoardShim.get_eeg_names(self.brainflow_id) # pull EEG channel data via brainflow API eeg_data = data[:, BoardShim.get_eeg_channels(self.brainflow_id)] timestamps = data[:, BoardShim.get_timestamp_channel(self.brainflow_id)] # Create a column for the stimuli to append to the EEG data stim_array = create_stim_array(timestamps, self.markers) timestamps = timestamps[ ..., None] # Add an additional dimension so that shapes match total_data = np.append(timestamps, eeg_data, 1) total_data = np.append(total_data, stim_array, 1) # Append the stim array to data. # Subtract five seconds of settling time from beginning total_data = total_data[5 * self.sfreq:] data_df = pd.DataFrame(total_data, columns=["timestamps"] + ch_names + ["stim"]) data_df.to_csv(self.save_fn, index=False) def _brainflow_push_sample(self, marker): last_timestamp = self.board.get_current_board_data(1)[-1][0] self.markers.append([marker, last_timestamp]) def start(self, fn, duration=None): """Starts the EEG device based on the defined backend. Parameters: fn (str): name of the file to save the sessions data to. """ if fn: self.save_fn = fn if self.backend == "brainflow": # Start brainflow backend self._start_brainflow() self.markers = [] elif self.backend == "muselsl": self._start_muse(duration) def push_sample(self, marker, timestamp): """Universal method for pushing a marker and its timestamp to store alongside the EEG data. Parameters: marker (int): marker number for the stimuli being presented. timestamp (float): timestamp of stimulus onset from time.time() function. """ if self.backend == "brainflow": self._brainflow_push_sample(marker=marker) elif self.backend == "muselsl": self._muse_push_sample(marker=marker, timestamp=timestamp) def stop(self): if self.backend == "brainflow": self._stop_brainflow() elif self.backend == "muselsl": pass
def _init_brainflow(self): """This function initializes the brainflow backend based on the input device name. It calls a utility function to determine the appropriate USB port to use based on the current operating system. Additionally, the system allows for passing a serial number in the case that they want to use either the BraintBit or the Unicorn EEG devices from the brainflow family. Parameters: serial_num (str or int): serial number for either the BrainBit or Unicorn devices. """ # Initialize brainflow parameters self.brainflow_params = BrainFlowInputParams() if self.device_name == "ganglion": self.brainflow_id = BoardIds.GANGLION_BOARD.value if self.serial_port == None: self.brainflow_params.serial_port = get_openbci_usb() # set mac address parameter in case if self.mac_address is None: print( "No MAC address provided, attempting to connect without one" ) else: self.brainflow_params.mac_address = self.mac_address elif self.device_name == "ganglion_wifi": self.brainflow_id = BoardIds.GANGLION_WIFI_BOARD.value if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr self.brainflow_params.ip_port = 6677 elif self.device_name == "cyton": self.brainflow_id = BoardIds.CYTON_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "cyton_wifi": self.brainflow_id = BoardIds.CYTON_WIFI_BOARD.value if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr self.brainflow_params.ip_port = 6677 elif self.device_name == "cyton_daisy": self.brainflow_id = BoardIds.CYTON_DAISY_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "cyton_daisy_wifi": self.brainflow_id = BoardIds.CYTON_DAISY_WIFI_BOARD.value if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr elif self.device_name == "brainbit": self.brainflow_id = BoardIds.BRAINBIT_BOARD.value elif self.device_name == "unicorn": self.brainflow_id = BoardIds.UNICORN_BOARD.value elif self.device_name == "callibri_eeg": self.brainflow_id = BoardIds.CALLIBRI_EEG_BOARD.value if self.other: self.brainflow_params.other_info = str(self.other) elif self.device_name == "notion1": self.brainflow_id = BoardIds.NOTION_1_BOARD.value elif self.device_name == "notion2": self.brainflow_id = BoardIds.NOTION_2_BOARD.value elif self.device_name == "freeeeg32": self.brainflow_id = BoardIds.FREEEEG32_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "synthetic": self.brainflow_id = BoardIds.SYNTHETIC_BOARD.value # some devices allow for an optional serial number parameter for better connection if self.serial_num: serial_num = str(self.serial_num) self.brainflow_params.serial_number = serial_num if self.serial_port: serial_port = str(self.serial_port) self.brainflow_params.serial_port = serial_port # Initialize board_shim self.sfreq = BoardShim.get_sampling_rate(self.brainflow_id) self.board = BoardShim(self.brainflow_id, self.brainflow_params) self.board.prepare_session()
def main(): BoardShim.enable_board_logger() BoardShim.enable_dev_board_logger() params = BrainFlowInputParams() # params.serial_port = "COM3" board = BoardShim(BoardIds.SYNTHETIC_BOARD.value, params) board.log_message(LogLevels.LEVEL_INFO.value, "Preparing session...") board.prepare_session() board.log_message(LogLevels.LEVEL_INFO.value, "Starting stream...") board.start_stream() start_time = time.time() run_time = 5 # 5 seconds board.log_message(LogLevels.LEVEL_INFO.value, "Start time = {}".format(start_time)) while time.time() - start_time < run_time: if board.get_board_data_count() > 0: data = board.get_board_data() print("Got data, time = {}, data count = {}.".format( time.time() - start_time, len(data))) # print(data[BoardShim.get_eeg_channels(BoardIds.CYTON_BOARD.value)]) time.sleep(0.001) board.log_message(LogLevels.LEVEL_INFO.value, "Stopping stream...") board.stop_stream() board.release_session()
import numpy as np import socket import platform import serial from brainflow import BoardShim, BoardIds # Default channel names for the various brainflow devices. EEG_CHANNELS = { "ganglion": ["fp1", "fp2", "tp7", "tp8"], "cyton": BoardShim.get_eeg_names(BoardIds.CYTON_BOARD.value), "cyton_daisy": BoardShim.get_eeg_names(BoardIds.CYTON_DAISY_BOARD.value), "brainbit": BoardShim.get_eeg_names(BoardIds.BRAINBIT_BOARD.value), "unicorn": BoardShim.get_eeg_names(BoardIds.UNICORN_BOARD.value), "synthetic": BoardShim.get_eeg_names(BoardIds.SYNTHETIC_BOARD.value), "notion1": BoardShim.get_eeg_names(BoardIds.NOTION_1_BOARD.value), "notion2": BoardShim.get_eeg_names(BoardIds.NOTION_2_BOARD.value), } BRAINFLOW_CHANNELS = { "ganglion": [], "cyton": EEG_CHANNELS["cyton"] + ["accel_0", "accel_1", "accel_2"], "cyton_daisy": EEG_CHANNELS["cyton_daisy"] + ["accel_0", "accel_1", "accel_2"], "synthetic": EEG_CHANNELS["synthetic"], } EEG_INDICES = { "muse2016": [1, 2, 3, 4], "muse2": [1, 2, 3, 4], "museS": [1, 2, 3, 4],
if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--serial-port', type=str, help='serial port', required=False, default='/dev/ttyUSB0') # if you are on Linux remember to give permission to access the port: # sudo chmod 666 /dev/ttyUSB0 # or change the user group # check BrainFlow documentation for Windows configs args = parser.parse_args() params = BrainFlowInputParams() params.serial_port = args.serial_port board = BoardShim(BoardIds.CYTON_BOARD.value, params) board.prepare_session() shared_vars = Shared() mutex = threading.Lock() board.start_stream() # use this for default options acquisition = threading.Thread(target=acquire_signals) acquisition.start() computing = threading.Thread(target=compute_signals) computing.start() acquisition.join() computing.join() board.stop_stream()
class EEG: def __init__(self, device=None, serial_port=None, serial_num=None, mac_addr=None): """ The initialization function takes the name of the EEG device and determines whether or not the device belongs to the Muse or Brainflow families and initializes the appropriate backend. Parameters: device (str): name of eeg device used for reading data. """ # determine if board uses brainflow or muselsl backend self.device_name = device self.serial_num = serial_num self.serial_port = serial_port self.mac_address = mac_addr self.backend = self._get_backend(self.device_name) self.initialize_backend() def initialize_backend(self): if self.backend == 'brainflow': self._init_brainflow() elif self.backend == 'muselsl': self._init_muselsl() def _get_backend(self, device_name): if (device_name in brainflow_devices): return 'brainflow' elif device_name in ['muse2016', 'muse2']: return 'muselsl' ##################### # MUSE functions # ##################### def _init_muselsl(self): # Currently there's nothing we need to do here. However keeping the # option open to add things with this init method. pass def _start_muse(self, duration): if sys.platform in ["linux", "linux2", "darwin"]: # Look for muses muses = list_muses() # self.muse = muses[0] # Start streaming process self.stream_process = Process(target=stream, args=(self.muses[0]['address'], )) self.stream_process.start() # Create markers stream outlet self.muse_StreamInfo = StreamInfo('Markers', 'Markers', 1, 0, 'int32', 'myuidw43536') self.muse_StreamOutlet = StreamOutlet(self.muse_StreamInfo) # Start a background process that will stream data from the first available Muse print("starting background recording process") print('will save to file: %s' % self.save_fn) self.recording = Process(target=record, args=(duration, self.save_fn)) self.recording.start() time.sleep(5) self.push_sample([99], timestamp=time.time()) def _stop_muse(self): pass def _muse_push_sample(self, marker, timestamp): self.muse_StreamOutlet.push_sample(marker, timestamp) ########################## # BrainFlow functions # ########################## def _init_brainflow(self): """ This function initializes the brainflow backend based on the input device name. It calls a utility function to determine the appropriate USB port to use based on the current operating system. Additionally, the system allows for passing a serial number in the case that they want to use either the BraintBit or the Unicorn EEG devices from the brainflow family. Parameters: serial_num (str or int): serial number for either the BrainBit or Unicorn devices. """ # Initialize brainflow parameters self.brainflow_params = BrainFlowInputParams() if self.device_name == 'ganglion': self.brainflow_id = BoardIds.GANGLION_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() # set mac address parameter in case if self.mac_address is not None: self.brainflow_params.mac_address = self.mac_address else: print( "No MAC address provided, attempting to connect without one" ) elif self.device_name == 'ganglion_wifi': brainflow_id = BoardIds.GANGLION_WIFI_BOARD.value self.brainflow_params.ip_address, self.brainflow_params.ip_port = get_openbci_ip( ) elif self.device_name == 'cyton': self.brainflow_id = BoardIds.CYTON_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == 'cyton_wifi': self.brainflow_id = BoardIds.CYTON_WIFI_BOARD.value self.brainflow_params.ip_address, self.brainflow_params.ip_port = get_openbci_ip( ) elif self.device_name == 'cyton_daisy': self.brainflow_id = BoardIds.CYTON_DAISY_BOARD.value if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == 'cyton_daisy_wifi': self.brainflow_id = BoardIds.CYTON_DAISY_WIFI_BOARD.value self.brainflow_params.ip_address, self.brainflow_params.ip_port = get_openbci_ip( ) elif self.device_name == 'brainbit': self.brainflow_id = BoardIds.BRAINBIT_BOARD.value elif self.device_name == 'unicorn': self.brainflow_id = BoardIds.UNICORN_BOARD.value elif self.device_name == 'synthetic': self.brainflow_id = BoardIds.SYNTHETIC_BOARD.value if self.serial_num: serial_num = str(self.serial_num) self.brainflow_params.other_info = serial_num if self.serial_port: serial_port = str(self.serial_port) self.brainflow_params.serial_port = serial_port # Initialize board_shim self.sfreq = BoardShim.get_sampling_rate(self.brainflow_id) self.board = BoardShim(self.brainflow_id, self.brainflow_params) self.board.prepare_session() def _start_brainflow(self): self.board.start_stream() # wait for signal to settle sleep(5) def _stop_brainflow(self): """This functions kills the brainflow backend and saves the data to a CSV file.""" # Collect session data and kill session data = self.board.get_board_data() # will clear board buffer self.board.stop_stream() self.board.release_session() # transform data for saving data = data.T # transpose data ch_names = BRAINFLOW_CHANNELS[self.device_name] num_channels = len(ch_names) eeg_data = data[:, 1:num_channels + 1] timestamps = data[:, -1] # Create a column for the stimuli to append to the EEG data stim_array = create_stim_array(timestamps, self.markers) timestamps = timestamps[ ..., None] # Add an additional dimension so that shapes match total_data = np.append(timestamps, eeg_data, 1) total_data = np.append(total_data, stim_array, 1) # Append the stim array to data. # Subtract five seconds of settling time from beginning total_data = total_data[5 * self.sfreq:] data_df = pd.DataFrame(total_data, columns=['timestamps'] + ch_names + ['stim']) data_df.to_csv(self.save_fn, index=False) def _brainflow_push_sample(self, marker): last_timestamp = self.board.get_current_board_data(1)[-1][0] self.markers.append([marker, last_timestamp]) def start(self, fn, duration=None): """ Starts the EEG device based on the defined backend. Parameters: fn (str): name of the file to save the sessions data to. """ if fn: self.save_fn = fn if self.backend == 'brainflow': # Start brainflow backend self._start_brainflow() self.markers = [] elif self.backend == 'muselsl': self._start_muse(duration) def push_sample(self, marker, timestamp): """ Universal method for pushing a marker and its timestamp to store alongside the EEG data. Parameters: marker (int): marker number for the stimuli being presented. timestamp (float): timestamp of stimulus onset from time.time() function. """ if self.backend == 'brainflow': self._brainflow_push_sample(marker=marker) elif self.backend == 'muselsl': self._muse_push_sample(marker=marker, timestamp=timestamp) def stop(self): if self.backend == 'brainflow': self._stop_brainflow() elif self.backend == 'muselsl': pass
class EEG: """ A class used to wrap all the communication with the OpenBCI EEG ... Attributes ---------- board_id : int id of the OpenBCI board ip_port : int port for the board serial_port : str serial port for the board headset : str the headset name we use, will be presented in the metadata """ def __init__(self, board_id: int = BoardIds.CYTON_DAISY_BOARD.value, ip_port: int = 6677, serial_port: Optional[str] = None, headset: str = "avi13"): # Board Id and Headset Name self.board_id = board_id self.headset: str = headset # BrainFlowInputParams self.params = BrainFlowInputParams() self.params.ip_port = ip_port self.params.serial_port = serial_port if serial_port is not None else self.find_serial_port( ) self.params.headset = headset self.params.board_id = board_id self.board = BoardShim(board_id, self.params) # Other Params self.sfreq = self.board.get_sampling_rate(board_id) self.marker_row = self.board.get_marker_channel(self.board_id) self.eeg_names = self.get_board_names() def extract_trials(self, data: NDArray) -> [List[Tuple], List[int]]: """ The method get ndarray and extract the labels and durations from the data. :param data: the data from the board. :return: """ # Init params durations, labels = [], [] # Get marker indices markers_idx = np.where(data[self.marker_row, :] != 0)[0] # For each marker for idx in markers_idx: # Decode the marker status, label, _ = self.decode_marker(data[self.marker_row, idx]) if status == 'start': labels.append(label) durations.append((idx, )) elif status == 'stop': durations[-1] += (idx, ) return durations, labels def on(self): """Turn EEG On""" self.board.prepare_session() self.board.start_stream() def off(self): """Turn EEG Off""" self.board.stop_stream() self.board.release_session() def insert_marker(self, status: str, label: int, index: int): """Insert an encoded marker into EEG data""" marker = self.encode_marker(status, label, index) # encode marker self.board.insert_marker(marker) # insert the marker to the stream # print(f'Status: { status }, Marker: { marker }') # debug # print(f'Count: { self.board.get_board_data_count() }') # debug def _numpy_to_df(self, board_data: NDArray): """ gets a Brainflow-style matrix and returns a Pandas Dataframe :param board_data: NDAarray retrieved from the board :returns df: a dataframe with the data """ # create dictionary of <col index,col name> for renaming DF eeg_channels = self.board.get_eeg_channels(self.board_id) eeg_names = self.board.get_eeg_names(self.board_id) timestamp_channel = self.board.get_timestamp_channel(self.board_id) acceleration_channels = self.board.get_accel_channels(self.board_id) marker_channel = self.board.get_marker_channel(self.board_id) column_names = {} column_names.update(zip(eeg_channels, eeg_names)) column_names.update(zip(acceleration_channels, ['X', 'Y', 'Z'])) column_names.update({ timestamp_channel: "timestamp", marker_channel: "marker" }) df = pd.DataFrame(board_data.T) df.rename(columns=column_names) # drop unused channels df = df[column_names] # decode int markers df['marker'] = df['marker'].apply(self.decode_marker) df[['marker_status', 'marker_label', 'marker_index']] = pd.DataFrame(df['marker'].tolist(), index=df.index) return df def _board_to_mne(self, board_data: NDArray, ch_names: List[str]) -> mne.io.RawArray: """ Convert the ndarray board data to mne object :param board_data: raw ndarray from board :return: """ eeg_data = board_data / 1000000 # BrainFlow returns uV, convert to V for MNE # Creating MNE objects from BrainFlow data arrays ch_types = ['eeg'] * len(board_data) info = mne.create_info(ch_names=ch_names, sfreq=self.sfreq, ch_types=ch_types) raw = mne.io.RawArray(eeg_data, info, verbose=False) return raw def get_raw_data(self, ch_names: List[str]) -> mne.io.RawArray: """ The method returns dataframe with all the raw data, and empties the buffer :param ch_names: list[str] of channels to select :return: mne_raw data """ indices = [self.eeg_names.index(ch) for ch in ch_names] data = self.board.get_board_data()[indices] return self._board_to_mne(data, ch_names) def get_features(self, channels: List[str], selected_funcs: List[str], notch: float = 50, low_pass: float = 4, high_pass: float = 50) -> NDArray: """ Returns features of all data since last call to get_board_data method. :return features: NDArray of shape (1, n_features) """ # Get the raw data data = self.get_raw_data(ch_names=channels) # Filter data = self.filter_data(data, notch, low_pass, high_pass) # Extract features features = extract_features( data.get_data()[np.newaxis], self.sfreq, selected_funcs, {'pow_freq_bands__freq_bands': np.array([8, 10, 12.5, 30])}) return features def clear_board(self): """Clear all data from the EEG board""" # Get the data and don't save it self.board.get_board_data() def get_board_data(self) -> NDArray: """The method returns the data from board and remove it""" return self.board.get_board_data() def get_board_names(self) -> List[str]: """The method returns the board's channels""" if self.headset == "avi13": # return ['Fp1', 'Fp2', 'C3', 'C4', 'CP5', 'CP6', 'O1', 'O2', 'FC1', 'FC2', 'Cz', 'T8', 'FC5', 'FC6', 'CP1', 'CP2'] return [ 'CP2', 'FC2', 'CP6', 'C4', 'C3', 'CP5', 'FC1', 'CP1', 'Cz', 'FC6', 'T8', 'T7', 'FC5' ] else: return self.board.get_eeg_names(self.board_id) def get_board_channels(self, alternative=True) -> List[int]: """Get list with the channels locations as list of int""" if alternative: return self.board.get_eeg_channels(self.board_id)[:-3] else: return self.board.get_eeg_channels(self.board_id) def get_channels_data(self): """Get NDArray only with the channels data (without all the markers and other stuff)""" return self.board.get_board_data()[self.get_board_channels()] def find_serial_port(self) -> str: """ Return the string of the serial port to which the FTDI dongle is connected. If running in Synthetic mode, return "" Example: return "COM5" """ if self.board_id == BoardIds.SYNTHETIC_BOARD: return "" else: plist = serial.tools.list_ports.comports() FTDIlist = [ comport for comport in plist if comport.manufacturer == 'FTDI' ] if len(FTDIlist) > 1: raise LookupError( "More than one FTDI-manufactured device is connected. Please enter serial_port manually." ) if len(FTDIlist) < 1: raise LookupError( "FTDI-manufactured device not found. Please check the dongle is connected" ) return FTDIlist[0].name @staticmethod def filter_data(data: mne.io.RawArray, notch: float, low_pass: float, high_pass: float) -> mne.io.RawArray: # data.notch_filter(freqs=notch, verbose=False) data.filter(l_freq=low_pass, h_freq=high_pass, verbose=False) return data @staticmethod def encode_marker(status: str, label: int, index: int): """ Encode a marker for the EEG data. :param status: status of the stim (start/end) :param label: the label of the stim (right -> 0, left -> 1, idle -> 2, tongue -> 3, legs -> 4) :param index: index of the current label :return: """ markerValue = 0 if status == "start": markerValue += 1 elif status == "stop": markerValue += 2 else: raise ValueError("incorrect status value") markerValue += 10 * label markerValue += 100 * index return markerValue @staticmethod def decode_marker(marker_value: int) -> (str, int, int): """ Decode the marker and return a tuple with the status, label and index. Look for the encoder docs for explanation for each argument in the marker. :param marker_value: :return: """ if marker_value % 10 == 1: status = "start" marker_value -= 1 elif marker_value % 10 == 2: status = "stop" marker_value -= 2 else: raise ValueError("incorrect status value. Use start or stop.") label = ((marker_value % 100) - (marker_value % 10)) / 10 index = (marker_value - (marker_value % 100)) / 100 return status, int(label), int(index) @staticmethod def laplacian(data: NDArray, channels: List[str]): """ The method execute laplacian on the raw data. The laplacian was computed as follows: 1. C3 = C3 - mean(Cz + F3 + P3 + T3) 2. C4 = C4 - mean(Cz + F4 + P4 + T4) The data need to be (n_channel, n_samples) :return: """ # Dict with all the indices of the channels idx = {ch: channels.index(ch) for ch in channels} # C3 data[idx['C3']] -= (data[idx['Cz']] + data[idx['FC5']] + data[idx['FC1']] + data[idx['CP5']] + data[idx['CP1']]) / 5 # C4 data[idx['C4']] -= (data[idx['Cz']] + data[idx['FC2']] + data[idx['FC6']] + data[idx['CP2']] + data[idx['CP6']]) / 5 return data[[idx['C3'], idx['C4']]]
def run(self): # 线程执行函数 params = BrainFlowInputParams() board_id = self.board_set[0] self.c_value = [] params.serial_port = self.board_set[1] self.board = BoardShim(board_id, params) # 在线数据 #一导联数据和量导联数据 self.board.prepare_session() self.board.start_stream() time.sleep(3) n = 0 m = np.zeros(2) while self.is_on: time.sleep(1) data = self.board.get_current_board_data(self.board_set[3]) data = data[1:9] mean_data = np.tile( data.mean(axis=1).reshape(8, 1), (1, self.board_set[3])) # print(data.shape) data = data - mean_data if self.puanduan1: self.org_signal.emit(data) filter1_data = self.processing_step1.step_1_list[ self.pre_set[0][0]](data, self.pre_set[0]) filter2_data = self.processing_step2.step_2_list[ self.pre_set[1][0]](filter1_data, self.pre_set[1]) if self.puanduan2: # print(self.pre_set) self.pre_signal.emit(filter2_data) feature_data = self.feature.feature_list[self.class_set[0]]( filter2_data, self.class_set) # print(np.argmax(feature_data)) if self.puanduan3: self.feature_signal.emit(feature_data) # print(feature_data) result = np.argmax(feature_data) r = feature_data[result] # 更改部分(将程序中2s读取一次改为1s读取一次,每次512采样点改为256采样点) if result == 5: r = r + 0.1 elif result == 6: r = r + 0.05 if r > 0.5: n = n + 1 p = result + 1 print("分类结果:", p, " n = ", n) m = np.append(m, p) m = m[1:] if m[0] == m[1]: m[1] = 0 else: b = str(p) self.command_signal.emit(b) print("发送指令:", p)
class steadyStateEvokedPotentials: def __init__(self, paradigm='ssvep'): self.paradigm = paradigm self.board_id = None self.params = None self.board = None self.max_trials = 500 self._setup_trials() def initialize_eeg(self, board_type='synthetic', usb_port=None, ip_addr=None, ip_port=None, serial_num=None): BoardShim.enable_dev_board_logger() self.board_id, self.params = get_board_info(board_type, usb_port, ip_addr, ip_port, serial_num) self.board = BoardShim(self.board_id, self.params) self.board.prepare_session() def _setup_trials(self): self.stim_freq = np.random.binomial(1, 0.5, self.max_trials) self.trials = DataFrame( dict(stim_freq=self.stim_freq, timestamp=np.zeros(self.max_trials))) def _setup_graphics(self): soa = 3.0 self.mywin = visual.Window([3440, 1440], monitor='testMonitor', units="deg", wintype='pygame') if self.paradigm == 'ssvep': grating = visual.GratingStim(win=self.mywin, mask='circle', size=80, sf=0.2) grating_neg = visual.GratingStim(win=self.mywin, mask='circle', size=80, sf=0.2, phase=0.5) frame_rate = np.round(self.mywin.getActualFrameRate()) stim_patterns = [ init_flicker_stim(frame_rate, 2, soa), init_flicker_stim(frame_rate, 3, soa) ] print(stim_patterns) return grating, grating_neg, stim_patterns def _load_image(self, fn): return visual.ImageStim(win=self.mywin, image=fn) def run_trial(self, duration, subject, run): # session information iti = 0.5 soa = 3.0 jitter = 0.2 record_duration = np.float32(duration) print( "Beginning EEG Stream; Wait 5 seconds for signal to settle... \n") self.board.start_stream() sleep(5) # Get starting time-stamp by pulling the last sample from the board and using its time stamp last_sample = self.board.get_current_board_data(1) start = last_sample[-1][0] # setup graphics grating, grating_neg, stim_patterns = self._setup_graphics() # iterate through events for ii, trial in self.trials.iterrows(): # inter trial interval core.wait(iti + np.random.rand() * jitter) label = self.trials['stim_freq'].iloc[ii] last_sample = self.board.get_current_board_data(1) timestamp = last_sample[-1][0] self.trials.loc[ii, 'timestamp'] = timestamp for _ in range(int(stim_patterns[label]['n_cycles'])): grating.setAutoDraw(True) for _ in range(int(stim_patterns[label]['cycle'][0])): self.mywin.flip() grating.setAutoDraw(False) grating_neg.setAutoDraw(True) for _ in range(stim_patterns[label]['cycle'][1]): self.mywin.flip() grating_neg.setAutoDraw(False) # Offset self.mywin.flip() if len(event.getKeys()) > 0 or (time() - start) > record_duration: break event.clearEvents() # cleanup the session self.board.stop_stream() data = self.board.get_board_data() data_fn, event_fn = get_fns(subject, run, self.paradigm) print(event_fn) DataFilter.write_file(data, data_fn, 'w') self.mywin.close() self.trials.to_csv(event_fn)
import numpy as np import socket import platform from brainflow import BoardShim, BoardIds # Default channel names for the various brainflow devices. EEG_CHANNELS = { 'ganglion': ['fp1', 'fp2', 'tp7', 'tp8'], 'cyton': BoardShim.get_eeg_names(BoardIds.CYTON_BOARD.value), 'cyton_daisy': BoardShim.get_eeg_names(BoardIds.CYTON_DAISY_BOARD.value), 'brainbit': BoardShim.get_eeg_names(BoardIds.BRAINBIT_BOARD.value), 'unicorn': BoardShim.get_eeg_names(BoardIds.UNICORN_BOARD.value), 'synthetic': BoardShim.get_eeg_names(BoardIds.SYNTHETIC_BOARD.value), 'notion1': BoardShim.get_eeg_names(BoardIds.NOTION_1_BOARD.value), 'notion2': BoardShim.get_eeg_names(BoardIds.NOTION_2_BOARD.value), } BRAINFLOW_CHANNELS = { 'ganglion': [], 'cyton': EEG_CHANNELS['cyton'] + ['accel_0', 'accel_1', 'accel_2'], 'cyton_daisy': EEG_CHANNELS['cyton_daisy'] + ['accel_0', 'accel_1', 'accel_2'], 'synthetic': EEG_CHANNELS['synthetic'], } EEG_INDICES = { 'muse2016': [1, 2, 3, 4], 'muse2': [1, 2, 3, 4], 'museS': [1, 2, 3, 4], 'ganglion': BoardShim.get_eeg_channels(BoardIds.GANGLION_BOARD.value),
def _init_brainflow(self): """ This function initializes the brainflow backend based on the input device name. It calls a utility function to determine the appropriate USB port to use based on the current operating system. Additionally, the system allows for passing a serial number in the case that they want to use either the BraintBit or the Unicorn EEG devices from the brainflow family. Parameters: serial_num (str or int): serial number for either the BrainBit or Unicorn devices. """ # Initialize brainflow parameters self.brainflow_params = BrainFlowInputParams() if self.device_name == 'ganglion': self.brainflow_id = BoardIds.GANGLION_BOARD.value if self.serial_port == None: self.brainflow_params.serial_port = get_openbci_usb() # set mac address parameter in case if self.mac_address is not None: self.brainflow_params.mac_address = self.mac_address else: print("No MAC address provided, attempting to connect without one") elif self.device_name == 'ganglion_wifi': brainflow_id = BoardIds.GANGLION_WIFI_BOARD.value self.brainflow_params.ip_address, self.brainflow_params.ip_port = get_openbci_ip() elif self.device_name == 'cyton': self.brainflow_id = BoardIds.CYTON_BOARD.value if self.serial_port == None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == 'cyton_wifi': self.brainflow_id = BoardIds.CYTON_WIFI_BOARD.value self.brainflow_params.ip_address, self.brainflow_params.ip_port = get_openbci_ip() elif self.device_name == 'cyton_daisy': self.brainflow_id = BoardIds.CYTON_DAISY_BOARD.value if self.serial_port == None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == 'cyton_daisy_wifi': self.brainflow_id = BoardIds.CYTON_DAISY_WIFI_BOARD.value self.brainflow_params.ip_address, self.brainflow_params.ip_port = get_openbci_ip() elif self.device_name == 'brainbit': self.brainflow_id = BoardIds.BRAINBIT_BOARD.value elif self.device_name == 'unicorn': self.brainflow_id = BoardIds.UNICORN_BOARD.value elif self.device_name == 'synthetic': self.brainflow_id = BoardIds.SYNTHETIC_BOARD.value if self.serial_num: serial_num = str(self.serial_num) self.brainflow_params.other_info = serial_num if self.serial_port: serial_port=str(self.serial_port) self.brainflow_params.serial_port = serial_port # Initialize board_shim self.sfreq = BoardShim.get_sampling_rate(self.brainflow_id) self.board = BoardShim(self.brainflow_id, self.brainflow_params) self.board.prepare_session()
def window_size(self): """" Returns the window size in samples """ return self.visible_seconds * BoardShim.get_sampling_rate( self.board_id)
def samples_to_seconds(samples: np.ndarray, board_id: int) -> np.ndarray: """" Same as sample to second but with an array """ return samples / BoardShim.get_sampling_rate(board_id)
class eventRelatedPotential: def __init__(self, erp='n170'): self.erp = erp self.board_prepared = False self.board_id = None self.params = None self.board = None self.max_trials = 500 self._setup_trial() def initialize_eeg(self, board_type='synthetic', usb_port=None, ip_addr=None, ip_port=None, serial_num=None): self.board_id, self.params = get_board_info(board_type, usb_port, ip_addr, ip_port, serial_num) self.board = BoardShim(self.board_id, self.params) self.board.prepare_session() self.board_prepared = True def _setup_trial(self): if self.erp == 'n170': self.image_type = np.random.binomial(1, 0.5, self.max_trials) if self.erp == 'p300': self.image_type = np.random.binomial(1, 0.5, self.max_trials) self.trials = DataFrame( dict(image_type=self.image_type, timestamp=np.zeros(self.max_trials))) def _setup_task(self): if self.erp == 'n170': self.markernames = ['houses', 'faces'] self.markers = [1, 2] if self.erp == 'p300': self.markernames = ['nontargets', 'targets'] self.markers = [1, 2] def _setup_graphics(self): self.mywin = visual.Window([3440, 1440], monitor='testMonitor', units="deg") if self.erp == 'n170': faces = list( map(self._load_image, glob('stim/face_house/faces/*_3.jpg'))) houses = list( map(self._load_image, glob('stim/face_house/houses/*.3.jpg'))) self.stim = [houses, faces] if self.erp == 'p300': targets = list( map(self._load_image, glob('stim/cats_dogs/target-*.jpg'))) nontargets = list( map(self._load_image, glob('stim/cats_dogs/nontarget-*.jpg'))) self.stim = [nontargets, targets] def _load_image(self, fn): return visual.ImageStim(win=self.mywin, image=fn) def run_trial(self, duration, subject, run): if self.board_prepared == False: self.board.prepare_session() self.board_prepared = True # session information iti = 0.4 soa = 0.3 jitter = 0.2 record_duration = np.float32(duration) print( "Beginning EEG Stream; Wait 5 seconds for signal to settle... \n") self.board.start_stream() sleep(5) # Get starting time-stamp by pulling the last sample from the board and using its time stamp last_sample = self.board.get_current_board_data(1) start = last_sample[-1][0] # setup graphics self._setup_graphics() # iterate through events for ii, trial in self.trials.iterrows(): # inter trial interval core.wait(iti + np.random.rand() * jitter) label = self.trials['image_type'].iloc[ii] image = choice(self.stim[label]) image.draw() last_sample = self.board.get_current_board_data(1) timestamp = last_sample[-1][0] self.trials.loc[ii, 'timestamp'] = timestamp self.mywin.flip() # offset (Off-SET!) core.wait(soa) self.mywin.flip() if len(event.getKeys()) > 0 or (time() - start) > record_duration: break event.clearEvents() # cleanup the session self.board.stop_stream() #self.board_prepared = False data = self.board.get_board_data() data_fn, event_fn = get_fns(subject, run, self.erp) DataFilter.write_file(data, data_fn, 'w') self.mywin.close() self.trials.to_csv(event_fn)
'cyton': [0, 1, 2, 3, 4, 5, 6, 7], 'cyton_daisy': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 'brainbit': [], } STIM_INDICES = { 'muse2016': 5, 'muse2': 4, 'cyton': 11, 'cyton_daisy': 19, } SAMPLE_FREQS = { 'muse2016': 256, 'muse2': 256, 'cyton': BoardShim.get_sampling_rate(BoardIds.CYTON_BOARD.value), 'cyton_daisy': BoardShim.get_sampling_rate(BoardIds.CYTON_DAISY_BOARD.value), } def get_openbci_ip(address, port): """ Gets the default IP address for connecting to the OpenBCI wifi shield but also allows users to pass their own values to override the defaults. Parameters: address (str): ip address port (str or int): ip port """ if address == None: address = '192.168.4.1'
class BrainflowDevice(EEGDevice): # list of brainflow devices devices: List[str] = [ "ganglion", "ganglion_wifi", "cyton", "cyton_wifi", "cyton_daisy", "cyton_daisy_wifi", "brainbit", "unicorn", "synthetic", "brainbit", "notion1", "notion2", ] def __init__( self, device_name: str, serial_num=None, serial_port=None, mac_addr=None, other=None, ip_addr=None, ): EEGDevice.__init__(self, device_name) self.serial_num = serial_num self.serial_port = serial_port self.mac_address = mac_addr self.other = other self.ip_addr = ip_addr self.markers: List[Tuple[List[int], float]] = [] self._init_brainflow() def start(self, filename: str = None, duration=None, extras: dict = None) -> None: self.save_fn = filename def record(): sleep(duration) self._stop_brainflow() self.board.start_stream() if duration: logger.info( "Starting background recording process, will save to file: %s" % self.save_fn) self.recording = Process(target=lambda: record()) self.recording.start() def stop(self) -> None: self._stop_brainflow() def push_sample(self, marker: List[int], timestamp: float): last_timestamp = self.board.get_current_board_data(1)[-1][0] self.markers.append((marker, last_timestamp)) def check(self, max_uv_abs=200) -> List[str]: data = self.board.get_board_data() # will clear board buffer # print(data) channel_names = BoardShim.get_eeg_names(self.brainflow_id) # FIXME: _check_samples expects different (Muse) inputs checked = _check_samples(data.T, channel_names, max_uv_abs=max_uv_abs) # type: ignore bads = [ch for ch, ok in checked.items() if not ok] return bads def _init_brainflow(self) -> None: """ This function initializes the brainflow backend based on the input device name. It calls a utility function to determine the appropriate USB port to use based on the current operating system. Additionally, the system allows for passing a serial number in the case that they want to use either the BrainBit or the Unicorn EEG devices from the brainflow family. Parameters: serial_num (str or int): serial number for either the BrainBit or Unicorn devices. """ from eegnb.devices.utils import get_openbci_usb # Initialize brainflow parameters self.brainflow_params = BrainFlowInputParams() device_name_to_id = { "ganglion": BoardIds.GANGLION_BOARD.value, "ganglion_wifi": BoardIds.GANGLION_WIFI_BOARD.value, "cyton": BoardIds.CYTON_BOARD.value, "cyton_wifi": BoardIds.CYTON_WIFI_BOARD.value, "cyton_daisy": BoardIds.CYTON_DAISY_BOARD.value, "cyton_daisy_wifi": BoardIds.CYTON_DAISY_WIFI_BOARD.value, "brainbit": BoardIds.BRAINBIT_BOARD.value, "unicorn": BoardIds.UNICORN_BOARD.value, "callibri_eeg": BoardIds.CALLIBRI_EEG_BOARD.value, "notion1": BoardIds.NOTION_1_BOARD.value, "notion2": BoardIds.NOTION_2_BOARD.value, "synthetic": BoardIds.SYNTHETIC_BOARD.value, } # validate mapping assert all(name in device_name_to_id for name in self.devices) self.brainflow_id = device_name_to_id[self.device_name] if self.device_name == "ganglion": if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() # set mac address parameter in case if self.mac_address is None: logger.info( "No MAC address provided, attempting to connect without one" ) else: self.brainflow_params.mac_address = self.mac_address elif self.device_name in [ "ganglion_wifi", "cyton_wifi", "cyton_daisy_wifi" ]: if self.ip_addr is not None: self.brainflow_params.ip_address = self.ip_addr elif self.device_name in ["cyton", "cyton_daisy"]: if self.serial_port is None: self.brainflow_params.serial_port = get_openbci_usb() elif self.device_name == "callibri_eeg": if self.other: self.brainflow_params.other_info = str(self.other) # some devices allow for an optional serial number parameter for better connection if self.serial_num: self.brainflow_params.serial_number = str(self.serial_num) if self.serial_port: self.brainflow_params.serial_port = str(self.serial_port) # Initialize board_shim self.sfreq = BoardShim.get_sampling_rate(self.brainflow_id) self.board = BoardShim(self.brainflow_id, self.brainflow_params) self.board.prepare_session() def get_data(self) -> pd.DataFrame: from eegnb.devices.utils import create_stim_array data = self.board.get_board_data() # will clear board buffer # transform data for saving data = data.T # transpose data print(data) # get the channel names for EEG data if self.brainflow_id == BoardIds.GANGLION_BOARD.value: # if a ganglion is used, use recommended default EEG channel names ch_names = ["fp1", "fp2", "tp7", "tp8"] else: # otherwise select eeg channel names via brainflow API ch_names = BoardShim.get_eeg_names(self.brainflow_id) # pull EEG channel data via brainflow API eeg_data = data[:, BoardShim.get_eeg_channels(self.brainflow_id)] timestamps = data[:, BoardShim.get_timestamp_channel(self.brainflow_id)] # Create a column for the stimuli to append to the EEG data stim_array = create_stim_array(timestamps, self.markers) timestamps = timestamps[ ..., None] # Add an additional dimension so that shapes match total_data = np.append(timestamps, eeg_data, 1) total_data = np.append(total_data, stim_array, 1) # Append the stim array to data. # Subtract five seconds of settling time from beginning # total_data = total_data[5 * self.sfreq :] df = pd.DataFrame(total_data, columns=["timestamps"] + ch_names + ["stim"]) return df def _save(self) -> None: """Saves the data to a CSV file.""" assert self.save_fn df = self.get_data() df.to_csv(self.save_fn, index=False) def _stop_brainflow(self) -> None: """This functions kills the brainflow backend and saves the data to a CSV file.""" # Collect session data and kill session if self.save_fn: self._save() self.board.stop_stream() self.board.release_session()
class EEG: def __init__(self, board_id=BoardIds.CYTON_DAISY_BOARD.value, ip_port=6677, serial_port="COM3"): # Board params self.board_id = board_id self.params = BrainFlowInputParams() self.params.ip_port = ip_port self.params.serial_port = serial_port self.board = BoardShim(board_id, self.params) self.sfreq = self.board.get_sampling_rate(board_id) self.marker_row = self.board.get_marker_channel(self.board_id) self.eeg_names = self.board.get_eeg_names(board_id) # Features params # todo: get as arg self.features_params = {'channels': ['C03', 'C04']} def extract_trials(self, data: NDArray) -> [List[Tuple], List[int]]: """ The method get ndarray and extract the labels and durations from the data. :param data: the data from the board. :return: """ # Init params durations, labels = [], [] # Get marker indices markers_idx = np.where(data[self.marker_row, :] != 0)[0] # For each marker for idx in markers_idx: # Decode the marker status, label, _ = self.decode_marker(data[self.marker_row, idx]) if status == 'start': labels.append(label) durations.append((idx,)) elif status == 'stop': durations[-1] += (idx,) return durations, labels def on(self): """Turn EEG On""" self.board.prepare_session() self.board.start_stream() def off(self): """Turn EEG Off""" self.board.stop_stream() self.board.release_session() def insert_marker(self, status: str, label: int, index: int): """Insert an encoded marker into EEG data""" marker = self.encode_marker(status, label, index) # encode marker self.board.insert_marker(marker) # insert the marker to the stream # print(f'Status: { status }, Marker: { marker }') # debug # print(f'Count: { self.board.get_board_data_count() }') # debug def _numpy_to_df(self, board_data: NDArray): """ gets a Brainflow-style matrix and returns a Pandas Dataframe :param board_data: NDAarray retrieved from the board :returns df: a dataframe with the data """ # create dictionary of <col index,col name> for renaming DF eeg_channels = self.board.get_eeg_channels(self.board_id) eeg_names = self.board.get_eeg_names(self.board_id) timestamp_channel = self.board.get_timestamp_channel(self.board_id) acceleration_channels = self.board.get_accel_channels(self.board_id) marker_channel = self.board.get_marker_channel(self.board_id) column_names = {} column_names.update(zip(eeg_channels, eeg_names)) column_names.update(zip(acceleration_channels, ['X', 'Y', 'Z'])) column_names.update({timestamp_channel: "timestamp", marker_channel: "marker"}) df = pd.DataFrame(board_data.T) df.rename(columns=column_names) # drop unused channels df = df[column_names] # decode int markers df['marker'] = df['marker'].apply(self.decode_marker) df[['marker_status', 'marker_label', 'marker_index']] = pd.DataFrame(df['marker'].tolist(), index=df.index) return df def _board_to_mne(self, board_data: NDArray) -> mne.io.RawArray: """ Convert the ndarray board data to mne object :param board_data: raw ndarray from board :return: """ eeg_data = board_data / 1000000 # BrainFlow returns uV, convert to V for MNE # Creating MNE objects from BrainFlow data arrays ch_types = ['eeg'] * len(board_data) info = mne.create_info(ch_names=self.eeg_names, sfreq=self.sfreq, ch_types=ch_types) raw = mne.io.RawArray(eeg_data, info) return raw def get_raw_data(self, ch_names: List[str]) -> mne.io.RawArray: """ The method returns dataframe with all the raw data, and empties the buffer :param ch_names: list[str] of channels to select :return: mne_raw data """ indices = [self.eeg_names.index(ch) for ch in ch_names] data = self.board.get_board_data()[indices] return self._board_to_mne(data) def get_features(self, channels: List[str], selected_funcs: List[str], notch: float = 50, low_pass: float = 4, high_pass: float = 48) -> NDArray: """ Returns features of all data since last call to get_board_data method. :return features: NDArray of shape (1, n_features) """ # Get the raw data data = self.get_raw_data(ch_names=channels) # Filter data = self.filter_data(data, notch, low_pass, high_pass) # Extract features features = extract_features(data.get_data()[0][np.newaxis], self.sfreq, selected_funcs) return features def clear_board(self): """Clear all data from the EEG board""" # Get the data and don't save it self.board.get_board_data() def get_board_data(self) -> NDArray: """The method returns the data from board and remove it""" return self.board.get_board_data() def get_board_names(self) -> List[str]: """The method returns the board's channels""" return self.board.get_eeg_names(self.board_id) def get_board_channels(self) -> List[int]: """Get list with the channels locations as list of int""" return self.board.get_eeg_channels(self.board_id) @staticmethod def filter_data(data: mne.io.RawArray, notch: float, low_pass: float, high_pass: float) -> mne.io.RawArray: data.notch_filter(freqs=notch) data.filter(l_freq=low_pass, h_freq=None) data.filter(l_freq=None, h_freq=high_pass) return data @staticmethod def encode_marker(status: str, label: int, index: int): """ Encode a marker for the EEG data. :param status: status of the stim (start/end) :param label: the label of the stim (right -> 0, left -> 1, idle -> 2) :param index: index of the current label :return: """ markerValue = 0 if status == "start": markerValue += 1 elif status == "stop": markerValue += 2 else: raise ValueError("incorrect status value") markerValue += 10 * label markerValue += 100 * index return markerValue @staticmethod def decode_marker(marker_value: int): """ Decode the marker and return a tuple with the status, label and index. Look for the encoder docs for explanation for each argument in the marker. :param marker_value: :return: """ if marker_value % 10 == 1: status = "start" marker_value -= 1 elif marker_value % 10 == 2: status = "stop" marker_value -= 2 else: raise ValueError("incorrect status value") label = ((marker_value % 100) - (marker_value % 10)) / 10 index = (marker_value - (marker_value % 100)) / 100 return status, int(label), int(index)