Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment Class Refactor (update to #183), converting specific experiments to subclasses #184

Merged
merged 24 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions eegnb/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@

from eegnb.devices.eeg import EEG

from eegnb.experiments.visual_n170 import n170
from eegnb.experiments.visual_p300 import p300
from eegnb.experiments.visual_ssvep import ssvep
from eegnb.experiments import VisualN170
from eegnb.experiments import VisualP300
from eegnb.experiments import VisualSSVEP
from eegnb.experiments import AuditoryOddball
from eegnb.experiments.visual_cueing import cueing
from eegnb.experiments.visual_codeprose import codeprose
from eegnb.experiments.auditory_oddball import aob, diaconescu
from eegnb.experiments.auditory_oddball import diaconescu
from eegnb.experiments.auditory_ssaep import ssaep, ssaep_onefreq


# New Experiment Class structure has a different initilization, to be noted
experiments = {
"visual-N170": n170,
"visual-P300": p300,
"visual-SSVEP": ssvep,
"visual-N170": VisualN170(),
"visual-P300": VisualP300(),
"visual-SSVEP": VisualSSVEP(),
"visual-cue": cueing,
"visual-codeprose": codeprose,
"auditory-SSAEP orig": ssaep,
"auditory-SSAEP onefreq": ssaep_onefreq,
"auditory-oddball orig": aob,
"auditory-oddball orig": AuditoryOddball(),
"auditory-oddball diaconescu": diaconescu,
Comment on lines 21 to 30
Copy link
Collaborator

@ErikBjare ErikBjare Aug 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should not be instantiated globally, just leave the class in the dict and let the caller handle initialization.

So that, for example:

    "visual-N170": VisualN170(),

Just becomes

    "visual-N170": VisualN170,

}

Expand All @@ -42,7 +44,15 @@ def run_experiment(
):
if experiment in experiments:
module = experiments[experiment]
module.present(duration=record_duration, eeg=eeg_device, save_fn=save_fn) # type: ignore

# Condition added for different run types of old and new experiment class structure
if experiment == "visual-N170" or experiment == "visual-P300" or experiment == "visual-SSVEP" or experiment == "auditory-oddball orig":
module.duration = record_duration
module.eeg = eeg_device
module.save_fn = save_fn
module.run()
Comment on lines +49 to +53
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be replaced with:

if isinstance(experiment, BaseExperiment.__class__):
    exp = experiment(eeg=eeg, duration=record_duration, save_fn=save_fn)
    exp.run()

else:
module.present(duration=record_duration, eeg=eeg_device, save_fn=save_fn) # type: ignore
else:
print("\nError: Unknown experiment '{}'".format(experiment))
print("\nExperiment can be one of:")
Expand Down
57 changes: 29 additions & 28 deletions eegnb/devices/eeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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())
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -445,6 +448,4 @@ def get_recent(self, n_samples: int = 256):
sorted_cols = sorted(df.columns)
df = df[sorted_cols]


return df

14 changes: 7 additions & 7 deletions eegnb/devices/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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 = {
Expand Down Expand Up @@ -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),
Expand Down
Loading