From f208e086a7302ffec069ed1475faabbf6b95f9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Thu, 16 Jun 2022 16:24:10 +0200 Subject: [PATCH 1/3] fix: fixed import (brainflow updated API) --- eegnb/devices/eeg.py | 57 +++++++++++++++++++++--------------------- eegnb/devices/utils.py | 14 +++++------ 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/eegnb/devices/eeg.py b/eegnb/devices/eeg.py index 6b22a377..13fdb83a 100644 --- a/eegnb/devices/eeg.py +++ b/eegnb/devices/eeg.py @@ -14,11 +14,17 @@ import numpy as np import pandas as pd -from brainflow import BoardShim, BoardIds, BrainFlowInputParams +from brainflow.board_shim import BoardShim, BoardIds, BrainFlowInputParams from muselsl import stream, list_muses, record, constants as mlsl_cnsts from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop -from eegnb.devices.utils import get_openbci_usb, create_stim_array,SAMPLE_FREQS,EEG_INDICES,EEG_CHANNELS +from eegnb.devices.utils import ( + get_openbci_usb, + create_stim_array, + SAMPLE_FREQS, + EEG_INDICES, + EEG_CHANNELS, +) logger = logging.getLogger(__name__) @@ -39,18 +45,19 @@ "notion2", "freeeeg32", "crown", - "museS_bfn", # bfn = brainflow with native bluetooth; - "museS_bfb", # bfb = brainflow with BLED dongle bluetooth + "museS_bfn", # bfn = brainflow with native bluetooth; + "museS_bfb", # bfb = brainflow with BLED dongle bluetooth "muse2_bfn", "muse2_bfb", "muse2016_bfn", - "muse2016_bfb" + "muse2016_bfb", ] class EEG: device_name: str stream_started: bool = False + def __init__( self, device=None, @@ -85,8 +92,8 @@ def initialize_backend(self): 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 - # stream metadata into the eeg class + self._muse_get_recent() # run this at initialization to get some + # stream metadata into the eeg class def _get_backend(self, device_name): if device_name in brainflow_devices: @@ -126,7 +133,7 @@ def _start_muse(self, duration): 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.stream_started = True self.push_sample([99], timestamp=time.time()) @@ -137,7 +144,7 @@ def _stop_muse(self): def _muse_push_sample(self, marker, timestamp): self.muse_StreamOutlet.push_sample(marker, timestamp) - def _muse_get_recent(self, n_samples: int=256, restart_inlet: bool=False): + def _muse_get_recent(self, n_samples: int = 256, restart_inlet: bool = False): if self._muse_recent_inlet and not restart_inlet: inlet = self._muse_recent_inlet else: @@ -157,9 +164,8 @@ def _muse_get_recent(self, n_samples: int=256, restart_inlet: bool=False): self.info = info self.n_chans = n_chans - timeout = (n_samples/sfreq)+0.5 - samples, timestamps = inlet.pull_chunk(timeout=timeout, - max_samples=n_samples) + timeout = (n_samples / sfreq) + 0.5 + samples, timestamps = inlet.pull_chunk(timeout=timeout, max_samples=n_samples) samples = np.array(samples) timestamps = np.array(timestamps) @@ -260,13 +266,13 @@ def _init_brainflow(self): elif self.device_name == "muse2_bfn": self.brainflow_id = BoardIds.MUSE_2_BOARD.value - + elif self.device_name == "muse2_bfb": self.brainflow_id = BoardIds.MUSE_2_BLED_BOARD.value elif self.device_name == "muse2016_bfn": self.brainflow_id = BoardIds.MUSE_2016_BOARD.value - + elif self.device_name == "muse2016_bfb": self.brainflow_id = BoardIds.MUSE_2016_BLED_BOARD.value @@ -291,11 +297,13 @@ def _start_brainflow(self): # only start stream if non exists if not self.stream_started: self.board.start_stream() - + self.stream_started = True # wait for signal to settle - if (self.device_name.find("cyton") != -1) or (self.device_name.find("ganglion") != -1): + if (self.device_name.find("cyton") != -1) or ( + self.device_name.find("ganglion") != -1 + ): # wait longer for openbci cyton / ganglion sleep(10) else: @@ -314,21 +322,19 @@ def _stop_brainflow(self): # Create a column for the stimuli to append to the EEG data stim_array = create_stim_array(timestamps, self.markers) - timestamps = timestamps[ ..., None ] - + timestamps = timestamps[..., None] + # Add an additional dimension so that shapes match total_data = np.append(timestamps, eeg_data, 1) # Append the stim array to data. - total_data = np.append(total_data, stim_array, 1) - + total_data = np.append(total_data, stim_array, 1) + # 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 @@ -357,14 +363,12 @@ def _brainflow_extract(self, data): 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 - + return ch_names, eeg_data, timestamps def _brainflow_push_sample(self, marker): last_timestamp = self.board.get_current_board_data(1)[self.timestamp_channel][0] self.markers.append([marker, last_timestamp]) - def _brainflow_get_recent(self, n_samples=256): # initialize brainflow if not set @@ -405,7 +409,6 @@ def start(self, fn, duration=None): 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. @@ -445,6 +448,4 @@ def get_recent(self, n_samples: int = 256): sorted_cols = sorted(df.columns) df = df[sorted_cols] - return df - diff --git a/eegnb/devices/utils.py b/eegnb/devices/utils.py index 2c085340..8e01ad9a 100644 --- a/eegnb/devices/utils.py +++ b/eegnb/devices/utils.py @@ -3,14 +3,14 @@ import platform import serial -from brainflow import BoardShim, BoardIds +from brainflow.board_shim import BoardShim, BoardIds # Default channel names for the various EEG devices. EEG_CHANNELS = { - "muse2016": ['TP9', 'AF7', 'AF8', 'TP10', 'Right AUX'], - "muse2": ['TP9', 'AF7', 'AF8', 'TP10', 'Right AUX'], - "museS": ['TP9', 'AF7', 'AF8', 'TP10', 'Right AUX'], + "muse2016": ["TP9", "AF7", "AF8", "TP10", "Right AUX"], + "muse2": ["TP9", "AF7", "AF8", "TP10", "Right AUX"], + "museS": ["TP9", "AF7", "AF8", "TP10", "Right AUX"], "muse2016_bfn": BoardShim.get_eeg_names(BoardIds.MUSE_2016_BOARD.value), "muse2016_bfb": BoardShim.get_eeg_names(BoardIds.MUSE_2016_BLED_BOARD.value), "muse2_bfn": BoardShim.get_eeg_names(BoardIds.MUSE_2_BOARD.value), @@ -26,7 +26,7 @@ "notion1": BoardShim.get_eeg_names(BoardIds.NOTION_1_BOARD.value), "notion2": BoardShim.get_eeg_names(BoardIds.NOTION_2_BOARD.value), "crown": BoardShim.get_eeg_names(BoardIds.CROWN_BOARD.value), - "freeeeg32": [f'eeg_{i}' for i in range(0,32)], + "freeeeg32": [f"eeg_{i}" for i in range(0, 32)], } BRAINFLOW_CHANNELS = { @@ -62,9 +62,9 @@ "muse2016": 256, "muse2": 256, "museS": 256, - "muse2016_bfn": BoardShim.get_sampling_rate(BoardIds.MUSE_2016_BOARD.value), + "muse2016_bfn": BoardShim.get_sampling_rate(BoardIds.MUSE_2016_BOARD.value), "muse2016_bfb": BoardShim.get_sampling_rate(BoardIds.MUSE_2016_BLED_BOARD.value), - "muse2_bfn": BoardShim.get_sampling_rate(BoardIds.MUSE_2_BOARD.value), + "muse2_bfn": BoardShim.get_sampling_rate(BoardIds.MUSE_2_BOARD.value), "muse2_bfb": BoardShim.get_sampling_rate(BoardIds.MUSE_2_BLED_BOARD.value), "museS_bfn": BoardShim.get_sampling_rate(BoardIds.MUSE_S_BOARD.value), "museS_bfb": BoardShim.get_sampling_rate(BoardIds.MUSE_S_BLED_BOARD.value), From 453b85dc791d5a3fddfe1a0a8171fa535ea2009f Mon Sep 17 00:00:00 2001 From: John Griffiths Date: Tue, 19 Jul 2022 22:27:26 -0400 Subject: [PATCH 2/3] sqc fixes for unicorn (#176) --- eegnb/analysis/utils.py | 40 ++++++++++++++++++++++------------------ eegnb/cli/introprompt.py | 2 +- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/eegnb/analysis/utils.py b/eegnb/analysis/utils.py index bd67baa9..236be95f 100644 --- a/eegnb/analysis/utils.py +++ b/eegnb/analysis/utils.py @@ -32,6 +32,22 @@ logger = logging.getLogger(__name__) +# Empirically determined lower and upper bounds of +# acceptable temporal standard deviations +# for different EEG devices tested by us +openbci_devices = ['ganglion', 'ganglion_wifi', 'cyton', 'cyton_wifi', 'cyton_daisy_wifi'] +muse_devices = ['muse' + model + sfx for model in ['2016', '2', 'S'] for sfx in ['', '_bfn', '_bfb']] +neurosity_devices = ['notion1', 'notion2', 'crown'] +gtec_devices = ['unicorn'] +alltesteddevices = openbci_devices + muse_devices + neurosity_devices + gtec_devices +thres_stds = {} +for device in alltesteddevices: + if device in openbci_devices: thres_stds[device] = [1,9] + elif device in muse_devices: thres_stds[device] = [1,18] + elif device in neurosity_devices: thres_stds[device] = [1,15] + elif device in gtec_devices: thres_stds[device] = [1,15] + + def load_csv_as_raw( fnames: List[str], sfreq: float, @@ -445,25 +461,13 @@ def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, th # If no upper and lower std thresholds set in function call, # set thresholds based on the following per-device name defaults - if thres_std_high is None: - if eeg.device_name in ["ganglion", "ganglion_wifi", "cyton", - "cyton_wifi", "cyton_daisy", "cyton_daisy_wifi"]: - thres_std_high = 9 - elif eeg.device_name in ["notion1", "notion2", "crown"]: - thres_std_high = 15 - elif 'muse' in eeg.device_name: - thres_std_high = 18 - + edn = eeg.device_name if thres_std_low is None: - - if 'muse' in eeg.device_name: - thres_std_low = 1 - - elif eeg.device_name in ["ganglion", "ganglion_wifi", "cyton", - "cyton_wifi", "cyton_daisy", "cyton_daisy_wifi", - "notion1", "notion2", "crown"]: - thres_std_low = 1 - + if edn in thres_stds.keys(): + thres_std_low = thres_stds[edn][0] + if thres_std_high is None: + if edn in thres_stds.keys(): + thres_std_high = thres_stds[edn][1] print("\n\nRunning signal quality check...") print(f"Accepting threshold stdev between: {thres_std_low} - {thres_std_high}") diff --git a/eegnb/cli/introprompt.py b/eegnb/cli/introprompt.py index 6eaf90c9..5061bbc3 100644 --- a/eegnb/cli/introprompt.py +++ b/eegnb/cli/introprompt.py @@ -25,7 +25,7 @@ def device_prompt() -> EEG: "ganglion": "OpenBCI Ganglion", "cyton": "OpenBCI Cyton", "cyton_daisy": "OpenBCI Cyton + Daisy", - "unicord": "G.Tec Unicorn", + "unicorn": "G.Tec Unicorn", "brainbit": "BrainBit", "notion1": "Notion 1", "notion2": "Notion 2", From a22501228677df0f968c49a0a699207380b1bd50 Mon Sep 17 00:00:00 2001 From: John Griffiths Date: Thu, 21 Jul 2022 11:47:42 -0400 Subject: [PATCH 3/3] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c35cafef..be6ff0a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ pyriemann>=0.2.7 jupyter muselsl>=2.0.2 brainflow>=4.8.2 -gdown +gdown>=4.5.1 matplotlib>=3.3.3 pysocks>=1.7.1 pyserial>=3.5