From a192bcdf1cd4536f9e03bfb8b8e39b2bc1e186dd Mon Sep 17 00:00:00 2001 From: John Griffiths Date: Thu, 14 Apr 2022 10:16:09 -0400 Subject: [PATCH 01/35] started pipelines function --- eegnb/analysis/pipelines.py | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 eegnb/analysis/pipelines.py diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py new file mode 100644 index 00000000..04c452fa --- /dev/null +++ b/eegnb/analysis/pipelines.py @@ -0,0 +1,90 @@ +""" +Analysis Pipelines +""" + + +################################################################################################### +# Setup +# --------------------- + +# Some standard pythonic imports +import os +from collections import OrderedDict +import warnings +warnings.filterwarnings('ignore') + +# MNE functions +from mne import Epochs,find_events + +# EEG-Notebooks functions +from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.datasets import fetch_dataset + + + + + +""" +epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, + reject={'eeg': 5e-5}, preload=True, + verbose=False, picks=[0,1,2,3], + event_id = OrderedDict(House=1,Face=2) ) +""" + +"""" +plot_kwargs = dict(conditions = epoch_kwargs['event_id'], + ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3])) +""" + + + +# Manually adjust the ylims +def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True): + + + # Load data + # ---------------------------- + raw = load_csv_as_raw(fnames) + + + # Filtering and cleaning + # ---------------------------- + raw.filter(1,30, method='iir') + + + # Epoching + # ---------------------------- + + # Create an array containing the timestamps and type of each stimulus (i.e. face or house) + events = find_events(raw) + + # Create an MNE Epochs object representing all the epochs around stimulus presentation + epochs = Epochs(raw, events=events, **epoch_kwargs) + + if verbose: + print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) + + + + + # Epoch average + # ---------------------------- + + if do_plot: + + fig, ax = plot_conditions(epochs, **plot_kwargs) + + # Manually adjust the ylims + #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + + + # Make Plots + # ----------------------------- + #raw.plot_psd(fmin=1, fmax=30); + + + + From 0e029b4bcd110406027c6a5ed83747130d7f2070 Mon Sep 17 00:00:00 2001 From: John Griffiths Date: Thu, 14 Apr 2022 11:31:46 -0400 Subject: [PATCH 02/35] almost working simple function equivalents of nb scripts --- eegnb/analysis/pipelines.py | 52 +++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 04c452fa..7d6afe1b 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -17,7 +17,7 @@ from mne import Epochs,find_events # EEG-Notebooks functions -from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.analysis.utils import load_csv_as_raw,plot_conditions from eegnb.datasets import fetch_dataset @@ -25,28 +25,48 @@ """ + +# Usage: + + +from pipelines import compute_erp,make_erp_plot +import glob +from collections import OrderedDict + +fnames = glob.glob('C:/Users/eeg_lab/.eegnb/data/visual-N170/eegnb_examples/muse2016/subject0001/session001/*.csv') + +' epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, reject={'eeg': 5e-5}, preload=True, verbose=False, picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2) ) -""" -"""" +raw,erp = compute_erp(fnames,epoch_kwargs) + + + plot_kwargs = dict(conditions = epoch_kwargs['event_id'], ci=97.5, n_boot=1000, title='', diff_waveform=None, #(1, 2)) channel_order=[1,0,2,3])) -""" +make_erp_plot(epochs,plot_kwargs) + + + +""" # Manually adjust the ylims -def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True): +def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True,sfreq=256, + ch_ind=[1,2,3,4]): + + # Load data # ---------------------------- - raw = load_csv_as_raw(fnames) + raw = load_csv_as_raw(fnames,sfreq=sfreq,ch_ind=ch_ind) # Filtering and cleaning @@ -66,24 +86,22 @@ def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True) if verbose: print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) + return raw,epochs +def make_erp_plot(epochs,plot_kwargs): - # Epoch average - # ---------------------------- - - if do_plot: - fig, ax = plot_conditions(epochs, **plot_kwargs) + fig, ax = plot_conditions(epochs, **plot_kwargs) - # Manually adjust the ylims - #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) - #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + # Manually adjust the ylims + #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) - # Make Plots - # ----------------------------- - #raw.plot_psd(fmin=1, fmax=30); + # Make Plots + # ----------------------------- + #raw.plot_psd(fmin=1, fmax=30); 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 03/35] 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 04/35] 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 68c4578785616eedc342a9d6ce4e1f2a4d34671b Mon Sep 17 00:00:00 2001 From: Parvfect Date: Sun, 24 Jul 2022 22:45:30 +0100 Subject: [PATCH 05/35] Ignore pushes --- eegnb/analysis/pipelines.py | 8 --- eegnb/analysis/playing_around_n170.py | 72 +++++++++++++++++++++++++++ eegnb/analysis/playing_around_p300.py | 59 ++++++++++++++++++++++ 3 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 eegnb/analysis/playing_around_n170.py create mode 100644 eegnb/analysis/playing_around_p300.py diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 7d6afe1b..5bfffe18 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -22,13 +22,10 @@ - - """ # Usage: - from pipelines import compute_erp,make_erp_plot import glob from collections import OrderedDict @@ -52,8 +49,6 @@ make_erp_plot(epochs,plot_kwargs) - - """ @@ -62,8 +57,6 @@ def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True, ch_ind=[1,2,3,4]): - - # Load data # ---------------------------- raw = load_csv_as_raw(fnames,sfreq=sfreq,ch_ind=ch_ind) @@ -91,7 +84,6 @@ def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True, def make_erp_plot(epochs,plot_kwargs): - fig, ax = plot_conditions(epochs, **plot_kwargs) # Manually adjust the ylims diff --git a/eegnb/analysis/playing_around_n170.py b/eegnb/analysis/playing_around_n170.py new file mode 100644 index 00000000..77df8603 --- /dev/null +++ b/eegnb/analysis/playing_around_n170.py @@ -0,0 +1,72 @@ + + +# Setting up + +# Some standard pythonic imports +import os +from collections import OrderedDict +import warnings +import matplotlib.pyplot as plt + +warnings.filterwarnings('ignore') + +# MNE functions +from mne import Epochs,find_events + +# EEG-Notebooks functions +from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.datasets import fetch_dataset + +# Loading Data +eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') +n170_data_path = os.path.join(eegnb_data_path, 'visual-N170', 'eegnb_examples') + +# If dataset hasn't been downloaded yet, download it +if not os.path.isdir(n170_data_path): + fetch_dataset(data_dir=eegnb_data_path, experiment='visual-N170', site='eegnb_examples'); + +subject = 1 +session = 1 +raw = load_data(subject,session, + experiment='visual-N170', site='eegnb_examples', device_name='muse2016_bfn', + data_dir = eegnb_data_path) + + +# Visualising the power spectrum +raw.plot_psd() +plt.show() + +# Filtering +raw.filter(1,30, method='iir') +raw.plot_psd(fmin=1, fmax=30); +plt.show() + +# Epoching +# Create an array containing the timestamps and type of each stimulus (i.e. face or house) +events = find_events(raw) +event_id = {'House': 1, 'Face': 2} + +# Create an MNE Epochs object representing all the epochs around stimulus presentation +epochs = Epochs(raw, events=events, event_id=event_id, + tmin=-0.1, tmax=0.6, baseline=None, + reject={'eeg': 5e-5}, preload=True, + verbose=False, picks=[0,1,2,3]) +print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) +print(epochs) + + +# Epoch Average +conditions = OrderedDict() +conditions['House'] = [1] +conditions['Face'] = [2] + +fig, ax = plot_conditions(epochs, conditions=conditions, + ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3]) # reordering of epochs.ch_names according to [[0,2],[1,3]] of subplot axes + +# Manually adjust the ylims +for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) +for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + +plt.show() diff --git a/eegnb/analysis/playing_around_p300.py b/eegnb/analysis/playing_around_p300.py new file mode 100644 index 00000000..1f09037d --- /dev/null +++ b/eegnb/analysis/playing_around_p300.py @@ -0,0 +1,59 @@ + +# Setup +# Some standard pythonic imports +import os +from collections import OrderedDict +import warnings +warnings.filterwarnings('ignore') + +# MNE functions +from mne import Epochs,find_events + +# EEG-Notebooks functions +from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.datasets import fetch_dataset + + +# Loading the Data +eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') +p300_data_path = os.path.join(eegnb_data_path, 'visual-P300', 'eegnb_examples') + +# If dataset hasn't been downloaded yet, download it +if not os.path.isdir(p300_data_path): + fetch_dataset(data_dir=eegnb_data_path, experiment='visual-P300', site='eegnb_examples'); + + +subject = 1 +session = 1 +raw = load_data(subject,session, + experiment='visual-P300', site='eegnb_examples', device_name='muse2016', + data_dir = eegnb_data_path) + +# Visualizing the power spectrum +raw.plot_psd() + +# Filtering +raw.filter(1,30, method='iir') +raw.plot_psd(fmin=1, fmax=30); + +# Epoching +# Create an array containing the timestamps and type of each stimulus (i.e. face or house) +events = find_events(raw) +event_id = {'Non-Target': 1, 'Target': 2} +epochs = Epochs(raw, events=events, event_id=event_id, + tmin=-0.1, tmax=0.8, baseline=None, + reject={'eeg': 100e-6}, preload=True, + verbose=False, picks=[0,1,2,3]) + +print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) + +print(epochs) + +# Epoch Average +conditions = OrderedDict() +conditions['Non-target'] = [1] +conditions['Target'] = [2] + +fig, ax = plot_conditions(epochs, conditions=conditions, + ci=97.5, n_boot=1000, title='', + diff_waveform=(1, 2)) From 5dfb3bccf97dde6ad41604f9b4529fededfc71a9 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 26 Jul 2022 23:27:29 +0100 Subject: [PATCH 06/35] Trying to create a cli --- eegnb/analysis/pipelines.py | 63 ++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 5bfffe18..f29606f3 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -20,8 +20,6 @@ from eegnb.analysis.utils import load_csv_as_raw,plot_conditions from eegnb.datasets import fetch_dataset - - """ # Usage: @@ -40,8 +38,6 @@ raw,erp = compute_erp(fnames,epoch_kwargs) - - plot_kwargs = dict(conditions = epoch_kwargs['event_id'], ci=97.5, n_boot=1000, title='', diff_waveform=None, #(1, 2)) @@ -53,13 +49,12 @@ # Manually adjust the ylims -def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True,sfreq=256, +def compute_erp(raw, fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True,sfreq=256, ch_ind=[1,2,3,4]): - # Load data # ---------------------------- - raw = load_csv_as_raw(fnames,sfreq=sfreq,ch_ind=ch_ind) + #raw = load_csv_as_raw(fnames,sfreq=sfreq,ch_ind=ch_ind) # Filtering and cleaning @@ -79,7 +74,7 @@ def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True, if verbose: print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) - return raw,epochs + return epochs def make_erp_plot(epochs,plot_kwargs): @@ -87,14 +82,60 @@ def make_erp_plot(epochs,plot_kwargs): fig, ax = plot_conditions(epochs, **plot_kwargs) # Manually adjust the ylims - #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) - #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) # Make Plots # ----------------------------- - #raw.plot_psd(fmin=1, fmax=30); + raw.plot_psd(fmin=1, fmax=30) + plt.show() + + + +import glob +from collections import OrderedDict + + +# Loading Data + +from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.datasets import fetch_dataset +import matplotlib.pyplot as plt + +eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') +n170_data_path = os.path.join(eegnb_data_path, 'visual-N170', 'eegnb_examples') +# If dataset hasn't been downloaded yet, download it +if not os.path.isdir(n170_data_path): + fetch_dataset(data_dir=eegnb_data_path, experiment='visual-N170', site='eegnb_examples'); + +subject = 1 +session = 1 +raw = load_data(subject,session, + experiment='visual-N170', site='eegnb_examples', device_name='muse2016_bfn', + data_dir = eegnb_data_path) + + +# Visualising the power spectrum +raw.plot_psd() +plt.show() + + +fnames = glob.glob('C:/Users/eeg_lab/.eegnb/data/visual-N170/eegnb_examples/muse2016/subject0001/session001/*.csv') +epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, + reject={'eeg': 5e-5}, preload=True, + verbose=False, picks=[0,1,2,3], + event_id = OrderedDict(House=1,Face=2)) + +epochs = compute_erp(raw, fnames,epoch_kwargs) + +plot_kwargs = dict(conditions = epoch_kwargs['event_id'], + ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3]) + +make_erp_plot(epochs,plot_kwargs) From 2600826ddb95b55ca7d0cf0474e6f7d5c612ce44 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 28 Jul 2022 13:27:05 +0100 Subject: [PATCH 07/35] Stepping through the problem --- eegnb/analysis/pipelines.py | 78 +++++--------------------- eegnb/analysis/pipelines_2.py | 101 ++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 64 deletions(-) create mode 100644 eegnb/analysis/pipelines_2.py diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index f29606f3..bf2a8c57 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -20,41 +20,40 @@ from eegnb.analysis.utils import load_csv_as_raw,plot_conditions from eegnb.datasets import fetch_dataset -""" -# Usage: + + +""" +# Usage: from pipelines import compute_erp,make_erp_plot import glob from collections import OrderedDict - fnames = glob.glob('C:/Users/eeg_lab/.eegnb/data/visual-N170/eegnb_examples/muse2016/subject0001/session001/*.csv') - ' epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, reject={'eeg': 5e-5}, preload=True, verbose=False, picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2) ) - raw,erp = compute_erp(fnames,epoch_kwargs) - plot_kwargs = dict(conditions = epoch_kwargs['event_id'], ci=97.5, n_boot=1000, title='', diff_waveform=None, #(1, 2)) channel_order=[1,0,2,3])) - make_erp_plot(epochs,plot_kwargs) - """ # Manually adjust the ylims -def compute_erp(raw, fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True,sfreq=256, +def compute_erp(fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose=True,sfreq=256, ch_ind=[1,2,3,4]): + + + # Load data # ---------------------------- - #raw = load_csv_as_raw(fnames,sfreq=sfreq,ch_ind=ch_ind) + raw = load_csv_as_raw(fnames,sfreq=sfreq,ch_ind=ch_ind) # Filtering and cleaning @@ -74,68 +73,19 @@ def compute_erp(raw, fnames,epoch_kwargs,plot_kwargs=None,do_plot=False,verbose= if verbose: print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) - return epochs + return raw,epochs def make_erp_plot(epochs,plot_kwargs): + fig, ax = plot_conditions(epochs, **plot_kwargs) # Manually adjust the ylims - for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) - for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) # Make Plots # ----------------------------- - raw.plot_psd(fmin=1, fmax=30) - plt.show() - - - -import glob -from collections import OrderedDict - - -# Loading Data - -from eegnb.analysis.utils import load_data,plot_conditions -from eegnb.datasets import fetch_dataset -import matplotlib.pyplot as plt - -eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') -n170_data_path = os.path.join(eegnb_data_path, 'visual-N170', 'eegnb_examples') - -# If dataset hasn't been downloaded yet, download it -if not os.path.isdir(n170_data_path): - fetch_dataset(data_dir=eegnb_data_path, experiment='visual-N170', site='eegnb_examples'); - -subject = 1 -session = 1 -raw = load_data(subject,session, - experiment='visual-N170', site='eegnb_examples', device_name='muse2016_bfn', - data_dir = eegnb_data_path) - - -# Visualising the power spectrum -raw.plot_psd() -plt.show() - - -fnames = glob.glob('C:/Users/eeg_lab/.eegnb/data/visual-N170/eegnb_examples/muse2016/subject0001/session001/*.csv') -epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, - reject={'eeg': 5e-5}, preload=True, - verbose=False, picks=[0,1,2,3], - event_id = OrderedDict(House=1,Face=2)) - -epochs = compute_erp(raw, fnames,epoch_kwargs) - -plot_kwargs = dict(conditions = epoch_kwargs['event_id'], - ci=97.5, n_boot=1000, title='', - diff_waveform=None, #(1, 2)) - channel_order=[1,0,2,3]) - -make_erp_plot(epochs,plot_kwargs) - - - + #raw.plot_psd(fmin=1, fmax=30); \ No newline at end of file diff --git a/eegnb/analysis/pipelines_2.py b/eegnb/analysis/pipelines_2.py new file mode 100644 index 00000000..a3fe48a4 --- /dev/null +++ b/eegnb/analysis/pipelines_2.py @@ -0,0 +1,101 @@ + + +""" + +Want functionality of the following form, + +epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, + reject={'eeg': 5e-5}, preload=True, + verbose=False, picks=[0,1,2,3], + event_id = OrderedDict(House=1,Face=2) ) +raw,erp = compute_erp(fnames,epoch_kwargs) +plot_kwargs = dict(conditions = epoch_kwargs['event_id'], + ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3])) +make_erp_plot(epochs,plot_kwargs) + +""" + +# Kwargs accepts in the form of a dict +# Args accepts in the form of tuple + +import os +from collections import OrderedDict +import warnings +import matplotlib.pyplot as plt + + +def compute_erp(fnames, **epoch_kwargs): + """ + Function to retrive the raw data and compute the Event Related Potential + + Procedure + 1. Loads the data using file names and retrives if not already present + 2. Epochs the data + 3. Computes the ERP + 4. Returns the raw and ERP objects + + Usage: + epoch_kwargs = dict(experiment = 'visual-N170', tmin=-0.1, tmax=0.6, baseline=None, + reject={'eeg': 5e-5}, preload=True, + verbose=False, picks=[0,1,2,3], + event_id = OrderedDict(House=1,Face=2) ) + raw,erp = compute_erp(fnames,epoch_kwargs) + """ + + # Load the data + + # If dataset hasn't been downloaded yet, download it + if not os.path.isdir(fnames): + fetch_dataset(data_dir=fnames, experiment=experiment, site='eegnb_examples'); + + raw = load_data(subject,session, + experiment=experiment, site='eegnb_examples', device_name='muse2016_bfn', + data_dir = eegnb_data_path) + + # Otherwise simply load the data + else: + raw = load_csv_as_raw(fnames,**epoch_kwargs) + + subject = 1 + session = 1 + + # Epochs the data + epochs = Epochs(raw, **epoch_kwargs) + + # Don't know if we want to include this + print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) + print(epochs) + + # Compute the ERP + erp = epochs.average() + + return raw,erp + +def make_erp_plot(epochs, **plot_kwargs): + """ + Function to make the ERP plot + + Procedure + + Usage: + plot_kwargs = dict(conditions = epoch_kwargs['event_id'], + ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3])) + make_erp_plot(epochs,plot_kwargs) + """ + + fig, ax = plot_conditions(epochs, **plot_kwargs) + + # Manually adjust the ylims + for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + + + # Make Plots + raw.plot_psd(fmin=1, fmax=30) + plt.show() + + return \ No newline at end of file From c23156d1488e01680c129d4af98058a6b2802796 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 8 Aug 2022 17:11:25 +0100 Subject: [PATCH 08/35] First commit --- eegnb/analysis/pipelines.py | 148 ++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 eegnb/analysis/pipelines.py diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py new file mode 100644 index 00000000..df308f1b --- /dev/null +++ b/eegnb/analysis/pipelines.py @@ -0,0 +1,148 @@ +""" + +CLI Pipeline for Analysis of EEGNB Recorded Data + +To do: +1. Package parameters into a dictionary +2. Adapt for other experiments/create an easy user manual +3. Add additional analysis methods + +Usage (for downloaded datasets): + +Visual N170: +raw, epochs = load_eeg_data('visual-n170', device_name='muse2016_bfn') +make_erp_plot(epochs) + +Visual P300: +raw, epochs = load_eeg_data('visual-P300', device_name='muse2016', event_id={'Non-Target': 1, 'Target': 2}) +make_erp_plot(epochs, conditions=OrderedDict(NonTarget=[1],Target=[2])) + +Other Experiments to be added later eg. Visual SSVEP + +Loading Recorded Data : + +raw, epochs = load_eeg_data(experiment, subject, session, device_name, tmin, tmax, reject) +make_erp_plot(epochs, title) + +Changable parameters to adjust plot + +tmin, tmax - Start and end time of the epochs in seconds, relative to the time-locked event. +The closest or matching samples corresponding to the start and end time are included. + +reject - Reject epochs based on maximum peak-to-peak signal amplitude (PTP), +i.e. the absolute difference between the lowest and the highest signal value. + +ci - confidence interval + +n_boot - number of bootstrap samples + +""" + +# Some standard pythonic imports +import os +from collections import OrderedDict +import warnings +import matplotlib.pyplot as plt + +warnings.filterwarnings('ignore') + +# MNE functions +from mne import Epochs,find_events + +# EEG-Notebooks functions +from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.datasets import fetch_dataset + + +def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', tmin=-0.1, tmax=0.6, baseline=None, + reject={'eeg': 5e-5}, preload=True, verbose=False, + picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2)): + """ + Loads EEG data from the specified experiment, subject, session, and device. + Returns the raw and epochs objects. + + Procedure + 1. Loads the data using file names and retrives if not already present + 2. Epochs the data + 3. Computes the ERP + 4. Returns the raw and ERP objects + + Parameters + ---------- + experiment : Experiment Name + subject : Subject ID of performed experiment + session : Session ID of performed experiment + device_name : Device used for performed experiment + tmin : Start time of the epochs in seconds, relative to the time-locked event. + tmax : End time of the epochs in seconds, relative to the time-locked event. + baseline : Not very sure..? + reject : Rejection parameters for the epochs. + preload : If True, preload the epochs into memory. + verbose : If True, print out messages. + picks : Channels to include in the analysis. + event_id : Dictionary of event_id's for the epochs + """ + + # Loading Data + eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') + n170_data_path = os.path.join(eegnb_data_path, experiment, 'eegnb_examples') + + # If dataset hasn't been downloaded yet, download it + if not os.path.isdir(n170_data_path): + fetch_dataset(data_dir=eegnb_data_path, experiment=experiment, site='eegnb_examples') + + raw = load_data(subject,session, + experiment=experiment, site='eegnb_examples', device_name=device_name, + data_dir = eegnb_data_path) + + # Visualising the power spectrum + raw.plot_psd() + plt.show() + + raw.filter(1,30, method='iir') + raw.plot_psd(fmin=1, fmax=30) + plt.show() + + # Epoching + # Create an array containing the timestamps and type of each stimulus (i.e. face or house) + events = find_events(raw) + + # Create an MNE Epochs object representing all the epochs around stimulus presentation + epochs = Epochs(raw, events=events, event_id=event_id, + tmin=tmin, tmax=tmax, baseline=baseline, + reject=reject, preload=preload, + verbose=verbose, picks=picks) + + print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) + print(epochs) + + return raw, epochs + + +def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3]): + """ + Plots the ERP for the specified conditions. + + Parameters + ---------- + epochs : MNE Epochs object + conditions : OrderedDict holding the conditions to plot + ci: confidence interval + n_boot: number of bootstrap samples + title: title of the plot + diff_waveform: tuple of two integers indicating the channels to compare + channel_order: list of integers indicating the order of the channels to plot + """ + + fig, ax = plot_conditions(epochs, conditions=conditions, + ci=97.5, n_boot=1000, title='', + diff_waveform=None, #(1, 2)) + channel_order=[1,0,2,3]) # reordering of epochs.ch_names according to [[0,2],[1,3]] of subplot axes + + # Manually adjust the ylims + for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + + plt.show() From 60525ecdbd399ce069c9adce9a979b27f2c79c90 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 16 Aug 2022 16:03:49 +0100 Subject: [PATCH 09/35] Fixing pause in signal quality check --- eegnb/analysis/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/eegnb/analysis/utils.py b/eegnb/analysis/utils.py index 236be95f..f72995fe 100644 --- a/eegnb/analysis/utils.py +++ b/eegnb/analysis/utils.py @@ -523,7 +523,6 @@ def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, th print("\nStopping signal quality checks!") break - sleep(pause_time) From c7bbaefdb9118003433eeee2ef61af5ee9d9aca2 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Wed, 17 Aug 2022 16:13:46 +0100 Subject: [PATCH 10/35] Fixing Signal quality check problem --- eegnb/analysis/pipelines_2.py | 101 -------------------------- eegnb/analysis/playing_around_n170.py | 72 ------------------ eegnb/analysis/playing_around_p300.py | 59 --------------- eegnb/analysis/utils.py | 28 ++++--- eegnb/cli/__main__.py | 6 +- requirements.txt | 1 + 6 files changed, 22 insertions(+), 245 deletions(-) delete mode 100644 eegnb/analysis/pipelines_2.py delete mode 100644 eegnb/analysis/playing_around_n170.py delete mode 100644 eegnb/analysis/playing_around_p300.py diff --git a/eegnb/analysis/pipelines_2.py b/eegnb/analysis/pipelines_2.py deleted file mode 100644 index a3fe48a4..00000000 --- a/eegnb/analysis/pipelines_2.py +++ /dev/null @@ -1,101 +0,0 @@ - - -""" - -Want functionality of the following form, - -epoch_kwargs = dict(tmin=-0.1, tmax=0.6, baseline=None, - reject={'eeg': 5e-5}, preload=True, - verbose=False, picks=[0,1,2,3], - event_id = OrderedDict(House=1,Face=2) ) -raw,erp = compute_erp(fnames,epoch_kwargs) -plot_kwargs = dict(conditions = epoch_kwargs['event_id'], - ci=97.5, n_boot=1000, title='', - diff_waveform=None, #(1, 2)) - channel_order=[1,0,2,3])) -make_erp_plot(epochs,plot_kwargs) - -""" - -# Kwargs accepts in the form of a dict -# Args accepts in the form of tuple - -import os -from collections import OrderedDict -import warnings -import matplotlib.pyplot as plt - - -def compute_erp(fnames, **epoch_kwargs): - """ - Function to retrive the raw data and compute the Event Related Potential - - Procedure - 1. Loads the data using file names and retrives if not already present - 2. Epochs the data - 3. Computes the ERP - 4. Returns the raw and ERP objects - - Usage: - epoch_kwargs = dict(experiment = 'visual-N170', tmin=-0.1, tmax=0.6, baseline=None, - reject={'eeg': 5e-5}, preload=True, - verbose=False, picks=[0,1,2,3], - event_id = OrderedDict(House=1,Face=2) ) - raw,erp = compute_erp(fnames,epoch_kwargs) - """ - - # Load the data - - # If dataset hasn't been downloaded yet, download it - if not os.path.isdir(fnames): - fetch_dataset(data_dir=fnames, experiment=experiment, site='eegnb_examples'); - - raw = load_data(subject,session, - experiment=experiment, site='eegnb_examples', device_name='muse2016_bfn', - data_dir = eegnb_data_path) - - # Otherwise simply load the data - else: - raw = load_csv_as_raw(fnames,**epoch_kwargs) - - subject = 1 - session = 1 - - # Epochs the data - epochs = Epochs(raw, **epoch_kwargs) - - # Don't know if we want to include this - print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) - print(epochs) - - # Compute the ERP - erp = epochs.average() - - return raw,erp - -def make_erp_plot(epochs, **plot_kwargs): - """ - Function to make the ERP plot - - Procedure - - Usage: - plot_kwargs = dict(conditions = epoch_kwargs['event_id'], - ci=97.5, n_boot=1000, title='', - diff_waveform=None, #(1, 2)) - channel_order=[1,0,2,3])) - make_erp_plot(epochs,plot_kwargs) - """ - - fig, ax = plot_conditions(epochs, **plot_kwargs) - - # Manually adjust the ylims - for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) - for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) - - - # Make Plots - raw.plot_psd(fmin=1, fmax=30) - plt.show() - - return \ No newline at end of file diff --git a/eegnb/analysis/playing_around_n170.py b/eegnb/analysis/playing_around_n170.py deleted file mode 100644 index 77df8603..00000000 --- a/eegnb/analysis/playing_around_n170.py +++ /dev/null @@ -1,72 +0,0 @@ - - -# Setting up - -# Some standard pythonic imports -import os -from collections import OrderedDict -import warnings -import matplotlib.pyplot as plt - -warnings.filterwarnings('ignore') - -# MNE functions -from mne import Epochs,find_events - -# EEG-Notebooks functions -from eegnb.analysis.utils import load_data,plot_conditions -from eegnb.datasets import fetch_dataset - -# Loading Data -eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') -n170_data_path = os.path.join(eegnb_data_path, 'visual-N170', 'eegnb_examples') - -# If dataset hasn't been downloaded yet, download it -if not os.path.isdir(n170_data_path): - fetch_dataset(data_dir=eegnb_data_path, experiment='visual-N170', site='eegnb_examples'); - -subject = 1 -session = 1 -raw = load_data(subject,session, - experiment='visual-N170', site='eegnb_examples', device_name='muse2016_bfn', - data_dir = eegnb_data_path) - - -# Visualising the power spectrum -raw.plot_psd() -plt.show() - -# Filtering -raw.filter(1,30, method='iir') -raw.plot_psd(fmin=1, fmax=30); -plt.show() - -# Epoching -# Create an array containing the timestamps and type of each stimulus (i.e. face or house) -events = find_events(raw) -event_id = {'House': 1, 'Face': 2} - -# Create an MNE Epochs object representing all the epochs around stimulus presentation -epochs = Epochs(raw, events=events, event_id=event_id, - tmin=-0.1, tmax=0.6, baseline=None, - reject={'eeg': 5e-5}, preload=True, - verbose=False, picks=[0,1,2,3]) -print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) -print(epochs) - - -# Epoch Average -conditions = OrderedDict() -conditions['House'] = [1] -conditions['Face'] = [2] - -fig, ax = plot_conditions(epochs, conditions=conditions, - ci=97.5, n_boot=1000, title='', - diff_waveform=None, #(1, 2)) - channel_order=[1,0,2,3]) # reordering of epochs.ch_names according to [[0,2],[1,3]] of subplot axes - -# Manually adjust the ylims -for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) -for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) - -plt.show() diff --git a/eegnb/analysis/playing_around_p300.py b/eegnb/analysis/playing_around_p300.py deleted file mode 100644 index 1f09037d..00000000 --- a/eegnb/analysis/playing_around_p300.py +++ /dev/null @@ -1,59 +0,0 @@ - -# Setup -# Some standard pythonic imports -import os -from collections import OrderedDict -import warnings -warnings.filterwarnings('ignore') - -# MNE functions -from mne import Epochs,find_events - -# EEG-Notebooks functions -from eegnb.analysis.utils import load_data,plot_conditions -from eegnb.datasets import fetch_dataset - - -# Loading the Data -eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') -p300_data_path = os.path.join(eegnb_data_path, 'visual-P300', 'eegnb_examples') - -# If dataset hasn't been downloaded yet, download it -if not os.path.isdir(p300_data_path): - fetch_dataset(data_dir=eegnb_data_path, experiment='visual-P300', site='eegnb_examples'); - - -subject = 1 -session = 1 -raw = load_data(subject,session, - experiment='visual-P300', site='eegnb_examples', device_name='muse2016', - data_dir = eegnb_data_path) - -# Visualizing the power spectrum -raw.plot_psd() - -# Filtering -raw.filter(1,30, method='iir') -raw.plot_psd(fmin=1, fmax=30); - -# Epoching -# Create an array containing the timestamps and type of each stimulus (i.e. face or house) -events = find_events(raw) -event_id = {'Non-Target': 1, 'Target': 2} -epochs = Epochs(raw, events=events, event_id=event_id, - tmin=-0.1, tmax=0.8, baseline=None, - reject={'eeg': 100e-6}, preload=True, - verbose=False, picks=[0,1,2,3]) - -print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) - -print(epochs) - -# Epoch Average -conditions = OrderedDict() -conditions['Non-target'] = [1] -conditions['Target'] = [2] - -fig, ax = plot_conditions(epochs, conditions=conditions, - ci=97.5, n_boot=1000, title='', - diff_waveform=(1, 2)) diff --git a/eegnb/analysis/utils.py b/eegnb/analysis/utils.py index f72995fe..ee5b0215 100644 --- a/eegnb/analysis/utils.py +++ b/eegnb/analysis/utils.py @@ -7,6 +7,7 @@ from typing import Union, List, Dict from time import sleep, time from numpy.core.fromnumeric import std +import keyboard import pandas as pd import numpy as np @@ -462,6 +463,7 @@ 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 edn = eeg.device_name + flag = False if thres_std_low is None: if edn in thres_stds.keys(): thres_std_low = thres_stds[edn][0] @@ -497,7 +499,6 @@ def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, th print("\nSignal quality:") print(indicators) - bad_channels = [k for k, v in std_series.iteritems() if v < thres_std_low or v > thres_std_high ] if bad_channels: print(f"Bad channels: {', '.join(bad_channels)}") @@ -514,18 +515,21 @@ def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, th if (loop_index+1) % n_inarow == 0: print(f"\n\nLooks like you still have {len(bad_channels)} bad channels after {loop_index+1} tries\n") - prompt_start = time() - continue_sigqual = input("\nChecks will resume in %s seconds...Press 'c' (and ENTER key) if you want to stop adjusting for better quality.\n" %pause_time) - while time() < prompt_start + 5: - if continue_sigqual == 'c': - break - if continue_sigqual == 'c': - print("\nStopping signal quality checks!") - break - - - + prompt_time = time() + print(f"Starting next cycle in 5 seconds, press C and enter to cancel") + while time() < prompt_time + 5: + if keyboard.is_pressed('c'): + print("\nStopping signal quality checks!") + flag = True + break + if flag: + break +def create_analysis_report(data_path=None): + if not data_path: + print("Could not find file!") + + def fix_musemissinglines(orig_f,new_f=''): if new_f == '': new_f = orig_f.replace('.csv', '_fml.csv') diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index 6e57fbce..e7806d63 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -9,7 +9,7 @@ from .introprompt import intro_prompt from .utils import run_experiment from eegnb.devices.eeg import EEG -from eegnb.analysis.utils import check_report +from eegnb.analysis.utils import check_report, create_analysis_report @click.group(name="eegnb") @@ -35,6 +35,7 @@ def runexp( outfname: str = None, prompt: bool = False, dosigqualcheck = True, + generatereport = True ): """ Run experiment. @@ -81,6 +82,9 @@ def askforsigqualcheck(): print(f"\n\n\nExperiment complete! Recorded data is saved @ {outfname}") + if generatereport: + create_analysis_report(outfname) + diff --git a/requirements.txt b/requirements.txt index c35cafef..bd5b390a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ pyserial>=3.5 h5py>=3.1.0 pytest-shutil pyo>=1.0.3; platform_system == "Linux" +keyboard==0.13.5 # This might try to build from source on linux (since there are no wheels for Linux on PyPI) # You can pass `--find-links=https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04/` your `pip install` to use the prebuilt wheels at the link. From 3b6525accf99dcbf33aceca4ab6d1f32cfdfccbd Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 18 Aug 2022 15:39:56 +0100 Subject: [PATCH 11/35] fix the technical debt --- .ipynb_checkpoints/Untitled-checkpoint.ipynb | 6 ++ Untitled.ipynb | 65 +++++++++++++++++ eegnb/analysis/pipelines.py | 75 +++++++++++++++----- eegnb/analysis/utils.py | 24 +++---- eegnb/cli/__main__.py | 17 ++++- testing.py | 11 +++ 6 files changed, 166 insertions(+), 32 deletions(-) create mode 100644 .ipynb_checkpoints/Untitled-checkpoint.ipynb create mode 100644 Untitled.ipynb create mode 100644 testing.py diff --git a/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/.ipynb_checkpoints/Untitled-checkpoint.ipynb new file mode 100644 index 00000000..363fcab7 --- /dev/null +++ b/.ipynb_checkpoints/Untitled-checkpoint.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Untitled.ipynb b/Untitled.ipynb new file mode 100644 index 00000000..91750d1e --- /dev/null +++ b/Untitled.ipynb @@ -0,0 +1,65 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "id": "45858bef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "\n", + "def plot_thing():\n", + " t = np.arange(0,10)\n", + " s = np.ranrandintndint(1,20, size=10)\n", + " plt.plot(t,s)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index df308f1b..f662b298 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -47,16 +47,18 @@ warnings.filterwarnings('ignore') # MNE functions -from mne import Epochs,find_events +from mne import Epochs,find_events, create_info +from mne.io import RawArray # EEG-Notebooks functions -from eegnb.analysis.utils import load_data,plot_conditions +from eegnb.analysis.utils import load_data,plot_conditions, load_csv_as_raw, fix_musemissinglines from eegnb.datasets import fetch_dataset +from eegnb.devices.utils import EEG_INDICES, SAMPLE_FREQS def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', tmin=-0.1, tmax=0.6, baseline=None, - reject={'eeg': 5e-5}, preload=True, verbose=False, - picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2)): + reject={'eeg': 5e-5}, preload=True, verbose=1, + picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2), fnames=None, example=True): """ Loads EEG data from the specified experiment, subject, session, and device. Returns the raw and epochs objects. @@ -81,24 +83,44 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', verbose : If True, print out messages. picks : Channels to include in the analysis. event_id : Dictionary of event_id's for the epochs + fnames : File names of the experiment data, if not passed, example files are used """ - - # Loading Data - eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') - n170_data_path = os.path.join(eegnb_data_path, experiment, 'eegnb_examples') - # If dataset hasn't been downloaded yet, download it - if not os.path.isdir(n170_data_path): - fetch_dataset(data_dir=eegnb_data_path, experiment=experiment, site='eegnb_examples') + # If not using the example dataset, load the data from the specified experiment using load_csv_as_raw + if not example: + sfreq = SAMPLE_FREQS[device_name] + ch_ind = EEG_INDICES[device_name] + + # Generate file names if not passed + if fnames is None: + raw = load_data(subject_id=subject, session_nb=session, experiment=experiment, device_name=device_name, site="local", data_dir=os.path.join(os.path.expanduser('~/'),'.eegnb', 'data')) + + else: + # Replace Ch names has arbitarily been set to None + if device_name in ["muse2016", "muse2", "museS"]: + raw = load_csv_as_raw([fnames], sfreq=sfreq, ch_ind=ch_ind, aux_ind=[5], replace_ch_names=None, verbose=verbose) + else: + raw = load_csv_as_raw([fnames], sfreq=sfreq, ch_ind=ch_ind, replace_ch_names=None, verbose=verbose) + + # If using the example dataset, load the data from the example dataset + else: + # Loading Data + eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') + experiment_data_path = os.path.join(eegnb_data_path, experiment, 'eegnb_examples') + + # If dataset hasn't been downloaded yet, download it + if not os.path.isdir(experiment_data_path): + fetch_dataset(data_dir=eegnb_data_path, experiment=experiment, site='eegnb_examples') + + raw = load_data(subject,session, + experiment=experiment, site='eegnb_examples', device_name=device_name, + data_dir = eegnb_data_path) - raw = load_data(subject,session, - experiment=experiment, site='eegnb_examples', device_name=device_name, - data_dir = eegnb_data_path) # Visualising the power spectrum raw.plot_psd() plt.show() - + raw.filter(1,30, method='iir') raw.plot_psd(fmin=1, fmax=30) plt.show() @@ -142,7 +164,26 @@ def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n channel_order=[1,0,2,3]) # reordering of epochs.ch_names according to [[0,2],[1,3]] of subplot axes # Manually adjust the ylims - for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) - for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + # Convert to automatic by searching the max and min values of the ERP + + #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) + #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) + plt.autoscale() plt.show() + +def create_analysis_report(experiment, eegdevice, data_path=None): + + if not data_path: + print("Could not find file!") + return + + if eegdevice == 'muse2': + fix_musemissinglines(data_path) + + raw, epochs = load_eeg_data(experiment=experiment, device_name=eegdevice, fnames=data_path, example=False) + fig = make_erp_plot(epochs) + + # Store analysis report in a separate folder + plt.savefig("{}/_analysis_report.png".format(os.path.dirname(data_path))) + print("Image saved at {}/_analysis_report.png".format(os.path.dirname(data_path))) diff --git a/eegnb/analysis/utils.py b/eegnb/analysis/utils.py index ee5b0215..d0ca7c58 100644 --- a/eegnb/analysis/utils.py +++ b/eegnb/analysis/utils.py @@ -5,12 +5,15 @@ from collections import OrderedDict from glob import glob from typing import Union, List, Dict +from collections import Iterable from time import sleep, time from numpy.core.fromnumeric import std import keyboard +import os import pandas as pd import numpy as np +import matplotlib.pyplot as plt import seaborn as sns from mne import create_info, concatenate_raws from mne.io import RawArray @@ -23,8 +26,6 @@ from eegnb.devices.eeg import EEG from eegnb.devices.utils import EEG_INDICES, SAMPLE_FREQS - - # this should probably not be done here sns.set_context("talk") sns.set_style("white") @@ -85,6 +86,7 @@ def load_csv_as_raw( n_aux = 0 raw = [] + for fn in fnames: # Read the file data = pd.read_csv(fn) @@ -108,7 +110,7 @@ def load_csv_as_raw( # create MNE object info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq, verbose=1) raw.append(RawArray(data=data, info=info, verbose=verbose)) - + raws = concatenate_raws(raw, verbose=verbose) montage = make_standard_montage("standard_1005") raws.set_montage(montage) @@ -523,20 +525,18 @@ def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, th flag = True break if flag: - break - -def create_analysis_report(data_path=None): - if not data_path: - print("Could not find file!") - - + break + def fix_musemissinglines(orig_f,new_f=''): - if new_f == '': new_f = orig_f.replace('.csv', '_fml.csv') + #if new_f == '': new_f = orig_f.replace('.csv', '_fml.csv') + + # Overwriting + new_f = orig_f print('writing fixed file to %s' %new_f) - # Read oriignal file + # Read original file F = open(orig_f, 'r') Ls = F.readlines() diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index e7806d63..e60bac4e 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -8,8 +8,10 @@ from .introprompt import intro_prompt from .utils import run_experiment +from eegnb import generate_save_fn from eegnb.devices.eeg import EEG -from eegnb.analysis.utils import check_report, create_analysis_report +from eegnb.analysis.utils import check_report +from eegnb.analysis.pipelines import create_analysis_report @click.group(name="eegnb") @@ -60,6 +62,8 @@ def runexp( if prompt: eeg, experiment, recdur, outfname = intro_prompt() else: + # Random values for outfile for now + outfname = generate_save_fn(eegdevice, experiment,7, 7) if eegdevice == "ganglion": # if the ganglion is chosen a MAC address should also be provided eeg = EEG(device=eegdevice, mac_addr=macaddr) @@ -74,16 +78,23 @@ def askforsigqualcheck(): "Sorry, didn't recognize answer. " askforsigqualcheck() + def askforreportcheck(): + do_sigqual = input("\n\nGenerate Report? (y/n). Recommend y \n") + if do_sigqual != 'y': + generatereport= False + if dosigqualcheck: askforsigqualcheck() - + + if generatereport: + askforreportcheck() run_experiment(experiment, eeg, recdur, outfname) print(f"\n\n\nExperiment complete! Recorded data is saved @ {outfname}") if generatereport: - create_analysis_report(outfname) + create_analysis_report(experiment, eegdevice, outfname) diff --git a/testing.py b/testing.py new file mode 100644 index 00000000..9471086d --- /dev/null +++ b/testing.py @@ -0,0 +1,11 @@ + + +import matplotlib.pyplot as plt +import numpy as np +from eegnb.analysis.pipelines import load_eeg_data, make_erp_plot +from eegnb.analysis.utils import fix_musemissinglines + +file_path = r"C:\Users\Parv\.eegnb\data\visual-N170\local\muse2\subject0001\session004\recording_2022-08-15-19.09.37.csv" + +raw, epochs = load_eeg_data(experiment='visual-N170', subject=1, session=3, device_name='muse2', example=False) +make_erp_plot(epochs) From 290df2bd9fe7f2d9f081815359eb0a3a06022c5c Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 23 Aug 2022 17:07:03 +0100 Subject: [PATCH 12/35] Save path done for automated saving pdf --- Untitled.ipynb | 65 -------------------------- eegnb/analysis/analysis_report.py | 76 +++++++++++++++++++++++++++++++ eegnb/analysis/pipelines.py | 51 +++++++++++++++++++-- testing.py | 3 +- 4 files changed, 124 insertions(+), 71 deletions(-) delete mode 100644 Untitled.ipynb create mode 100644 eegnb/analysis/analysis_report.py diff --git a/Untitled.ipynb b/Untitled.ipynb deleted file mode 100644 index 91750d1e..00000000 --- a/Untitled.ipynb +++ /dev/null @@ -1,65 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 4, - "id": "45858bef", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "\n", - "def plot_thing():\n", - " t = np.arange(0,10)\n", - " s = np.ranrandintndint(1,20, size=10)\n", - " plt.plot(t,s)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py new file mode 100644 index 00000000..1fb0dd5a --- /dev/null +++ b/eegnb/analysis/analysis_report.py @@ -0,0 +1,76 @@ +""" +File that has the functions to generate the analysis report pdf from the images + +Usage Instructions typically :: + +from eegnb.analysis.analysis_report import PDF +pdf = PDF() +pdf.alias_nb_pages() +pdf.add_page() +# Do whatever you want to add to the pdf +pdf.save_as_report() +""" + +from fpdf import FPDF +import matplotlib.pyplot as plt +import numpy as np +import os + +class PDF(FPDF): + def header(self): + + # Arial bold 15 + self.set_font('Arial', 'B', 15) + # Move to the right + self.cell(80) + # Title + self.cell(50, 10, 'Analysis Report', 1, 0, 'C') + # Line break + self.ln(20) + + # Page footer + def footer(self): + # Position at 1.5 cm from bottom + self.set_y(-15) + # Arial italic 8 + self.set_font('Arial', 'I', 8) + # Page number + self.cell(0, 10, 'Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C') + + def add_figure(self, fig_path, x, y, w, h): + + # Add figure to document + self.image(fig_path, x=x, y=y, w=w, h=h) + + # Delete figure from memory + os.remove(fig_path) + def add_matplotlib_figure(self, fig_path): + + # Add figure to document + self.image(fig_path, x=60, y=200, w=100, h=100) + + # Delete figure from memory + #os.remove(fig_path) + + +if __name__ == "__main__": + + # Instantiation of inherited class + pdf = PDF() + pdf.alias_nb_pages() + pdf.add_page() + pdf.imagex() + # Create random figure with matplotlib + fig = plt.figure() + ax = fig.add_subplot(111) + ax.plot(np.random.rand(100), '-') + ax.set_title('Random Plot') + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.grid(True) + + # Convert figure to pdf page + plt.savefig('books_read.png') + + pdf.add_matplotlib_figure('books_read.png') + pdf.output('tuto2.pdf', 'F') diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index f662b298..9362db57 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -43,6 +43,7 @@ from collections import OrderedDict import warnings import matplotlib.pyplot as plt +from datetime import datetime warnings.filterwarnings('ignore') @@ -51,10 +52,15 @@ from mne.io import RawArray # EEG-Notebooks functions +from eegnb import generate_save_fn from eegnb.analysis.utils import load_data,plot_conditions, load_csv_as_raw, fix_musemissinglines from eegnb.datasets import fetch_dataset from eegnb.devices.utils import EEG_INDICES, SAMPLE_FREQS +from eegnb.analysis.analysis_report import PDF +from pathlib import Path +DATA_DIR = os.path.join(os.path.expanduser("~/"), ".eegnb", "data") +eegdevice, experiment_name, subject_id, session_nb = None, None, None, None def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', tmin=-0.1, tmax=0.6, baseline=None, reject={'eeg': 5e-5}, preload=True, verbose=1, @@ -86,6 +92,9 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', fnames : File names of the experiment data, if not passed, example files are used """ + global eegdevice, experiment_name, subject_id, session_nb + eegdevice, experiment_name = device_name, experiment + # If not using the example dataset, load the data from the specified experiment using load_csv_as_raw if not example: sfreq = SAMPLE_FREQS[device_name] @@ -94,7 +103,8 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # Generate file names if not passed if fnames is None: raw = load_data(subject_id=subject, session_nb=session, experiment=experiment, device_name=device_name, site="local", data_dir=os.path.join(os.path.expanduser('~/'),'.eegnb', 'data')) - + subject_id, session_nb = subject, session + else: # Replace Ch names has arbitarily been set to None if device_name in ["muse2016", "muse2", "museS"]: @@ -102,8 +112,12 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', else: raw = load_csv_as_raw([fnames], sfreq=sfreq, ch_ind=ch_ind, replace_ch_names=None, verbose=verbose) + # Getting the subject and session + subject_id, session_nb = fnames.split('_')[1], fnames.split('_')[2] + # If using the example dataset, load the data from the example dataset else: + subject_id, session_nb = subject, session # Loading Data eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') experiment_data_path = os.path.join(eegnb_data_path, experiment, 'eegnb_examples') @@ -116,13 +130,13 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', experiment=experiment, site='eegnb_examples', device_name=device_name, data_dir = eegnb_data_path) - # Visualising the power spectrum - raw.plot_psd() - plt.show() + raw.plot_psd(show=False) + #plt.show() raw.filter(1,30, method='iir') - raw.plot_psd(fmin=1, fmax=30) + fig2=raw.plot_psd(fmin=1, fmax=30, show=False) + fig2.savefig("power_spectrum.png") plt.show() # Epoching @@ -170,7 +184,24 @@ def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) plt.autoscale() + plt.savefig("erp_plot.png") plt.show() + + + create_pdf() + +def create_pdf(): + """ + Creates a pdf of the figure. + """ + pdf = PDF() + pdf.alias_nb_pages() + pdf.add_page() + pdf.add_figure(os.getcwd()+'\\power_spectrum.png', 50, 50, 100, 100) + pdf.add_figure(os.getcwd()+'\\erp_plot.png', 50, 180, 100, 100) + save_dir = get_analysis_save_directory(experiment=experiment_name, eegdevice=eegdevice, subject=subject_id, session=session_nb) + pdf.output(os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))), 'F') + #pdf.output('{}\\analysis_report_{}.pdf'.format(save_dir, datetime.now().strftime("%d/%m/%Y_%H-%M-%S")), 'F') def create_analysis_report(experiment, eegdevice, data_path=None): @@ -187,3 +218,13 @@ def create_analysis_report(experiment, eegdevice, data_path=None): # Store analysis report in a separate folder plt.savefig("{}/_analysis_report.png".format(os.path.dirname(data_path))) print("Image saved at {}/_analysis_report.png".format(os.path.dirname(data_path))) + + +def get_analysis_save_directory(experiment, eegdevice, subject, session, site="local"): + + analysis_path = os.path.join(os.path.expanduser("~/"),'.eegnb', 'analysis') + report_path = os.path.join(analysis_path, experiment, site, eegdevice, "subject{}".format(subject), "session{}".format(session)) + if not os.path.isdir(report_path): + os.makedirs(report_path) + return report_path + #return os.path.join(report_path, "analysis_report_{}".format(datetime.now().strftime("%d/%m/%Y_%H-%M-%S"))) \ No newline at end of file diff --git a/testing.py b/testing.py index 9471086d..68cd0d68 100644 --- a/testing.py +++ b/testing.py @@ -7,5 +7,6 @@ file_path = r"C:\Users\Parv\.eegnb\data\visual-N170\local\muse2\subject0001\session004\recording_2022-08-15-19.09.37.csv" -raw, epochs = load_eeg_data(experiment='visual-N170', subject=1, session=3, device_name='muse2', example=False) +raw, epochs = load_eeg_data(experiment='visual-N170', subject=1, session=4, device_name='muse2', example=False) make_erp_plot(epochs) + From d2f7bc50c4be1d6a4cc4d37b47b250698d4c8ad0 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 23 Aug 2022 23:26:15 +0100 Subject: [PATCH 13/35] I feel amazing --- eegnb/analysis/analysis_report.py | 16 ++++-- eegnb/analysis/pipelines.py | 84 +++++++++++++++++++++++-------- testing.py | 2 +- 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 1fb0dd5a..6220e24e 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -20,11 +20,12 @@ class PDF(FPDF): def header(self): # Arial bold 15 - self.set_font('Arial', 'B', 15) + self.set_font('Arial', 'B', 25) + self.set_text_color(183, 208, 332) # Move to the right - self.cell(80) + #self.cell(80) # Title - self.cell(50, 10, 'Analysis Report', 1, 0, 'C') + self.cell(0, 10, 'Analysis Report') # Line break self.ln(20) @@ -37,13 +38,18 @@ def footer(self): # Page number self.cell(0, 10, 'Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C') - def add_figure(self, fig_path, x, y, w, h): + def add_figure(self, fig_path, x, y, w, h, title): + + #self.cell(w=100,h=10, txt=title, ln=0, align='C') + self.set_font('Times', 'B', 20) # Add figure to document + self.cell(0, 10, txt=title, ln=2, align='C') self.image(fig_path, x=x, y=y, w=w, h=h) - + self.ln(h+10) # Delete figure from memory os.remove(fig_path) + def add_matplotlib_figure(self, fig_path): # Add figure to document diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 9362db57..a069cdc2 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -92,17 +92,23 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', fnames : File names of the experiment data, if not passed, example files are used """ + # Parameters that are used in creating the save directory for the analysis report and hence global variables global eegdevice, experiment_name, subject_id, session_nb + + # Set the global variables based on the parameter values eegdevice, experiment_name = device_name, experiment # If not using the example dataset, load the data from the specified experiment using load_csv_as_raw if not example: + + # Obataining the specific parameters to load the data into MNE object sfreq = SAMPLE_FREQS[device_name] ch_ind = EEG_INDICES[device_name] # Generate file names if not passed if fnames is None: raw = load_data(subject_id=subject, session_nb=session, experiment=experiment, device_name=device_name, site="local", data_dir=os.path.join(os.path.expanduser('~/'),'.eegnb', 'data')) + # Setting the session id and session number for the analysis report subject_id, session_nb = subject, session else: @@ -118,6 +124,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # If using the example dataset, load the data from the example dataset else: subject_id, session_nb = subject, session + # Loading Data eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') experiment_data_path = os.path.join(eegnb_data_path, experiment, 'eegnb_examples') @@ -132,11 +139,16 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # Visualising the power spectrum raw.plot_psd(show=False) - #plt.show() + # Filtering the data under a certain frequency range raw.filter(1,30, method='iir') - fig2=raw.plot_psd(fmin=1, fmax=30, show=False) - fig2.savefig("power_spectrum.png") + + # Visualising the power spectrum + fig = raw.plot_psd(fmin=1, fmax=30, show=False) + + # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. + fig.savefig("power_spectrum.png") + plt.show() # Epoching @@ -177,54 +189,84 @@ def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n diff_waveform=None, #(1, 2)) channel_order=[1,0,2,3]) # reordering of epochs.ch_names according to [[0,2],[1,3]] of subplot axes - # Manually adjust the ylims - # Convert to automatic by searching the max and min values of the ERP - + """ + Axis scaling needs to be sorted out + """ #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) plt.autoscale() + + # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. plt.savefig("erp_plot.png") plt.show() - + # Creating the pdf, needs to be discussed whether we want to call it here or seperately. create_pdf() def create_pdf(): - """ - Creates a pdf of the figure. - """ + """Creates analysis report using the power spectrum and ERP plots that are saved in the directory""" + + # Initialzing the pdf pdf = PDF() pdf.alias_nb_pages() pdf.add_page() - pdf.add_figure(os.getcwd()+'\\power_spectrum.png', 50, 50, 100, 100) - pdf.add_figure(os.getcwd()+'\\erp_plot.png', 50, 180, 100, 100) + + """ + Note that added images to the pdf are deleted form their directory, as of now, these + images are temporarily created when plotted so they can be integrated into the pdf. + """ + + # Including experiment name, subject id and session number in the pdf + """ + pdf.set_font('Arial', 'B', 10) + pdf.cell(0, 10, 'Experiment - {}'.format(experiment_name)) + pdf.cell(0, 10, 'Subject - {}'.format(subject_id)) + pdf.cell(0, 10, 'Session - {}'.format(session_nb)) + """ + + #pdf.cell(0, 10, 'Power Spectrum', ln=1) + # Adding the Power Spectrum plot to the file + pdf.add_figure(os.getcwd()+'\\power_spectrum.png', x=10, y=40, w=180, h=100, title="Power Spectrum") + + #pdf.cell(0, 10, 'ERP', ln=1) + # Adding the ERP plot to the file + pdf.add_figure(os.getcwd()+'\\erp_plot.png', x=10, y=160, w=200, h=120, title="ERP Plot") + + # Getting the directory where the report should be saved save_dir = get_analysis_save_directory(experiment=experiment_name, eegdevice=eegdevice, subject=subject_id, session=session_nb) + + # Saving report pdf.output(os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))), 'F') - #pdf.output('{}\\analysis_report_{}.pdf'.format(save_dir, datetime.now().strftime("%d/%m/%Y_%H-%M-%S")), 'F') -def create_analysis_report(experiment, eegdevice, data_path=None): +def create_analysis_report(experiment, eegdevice, data_path=None, bluemuse_file_fix=False): + # Fault check if the data path is not specified, alternatively could make it a necessary parameter if not data_path: print("Could not find file!") return - if eegdevice == 'muse2': + # Fixing the muse2 recording issue if it was recorded using bluemuse + if bluemuse_file_fix: fix_musemissinglines(data_path) + # Loading the data raw, epochs = load_eeg_data(experiment=experiment, device_name=eegdevice, fnames=data_path, example=False) - fig = make_erp_plot(epochs) + make_erp_plot(epochs) - # Store analysis report in a separate folder - plt.savefig("{}/_analysis_report.png".format(os.path.dirname(data_path))) - print("Image saved at {}/_analysis_report.png".format(os.path.dirname(data_path))) + # Creating the analysis report, called automatically when erp plot is made + #create_pdf() def get_analysis_save_directory(experiment, eegdevice, subject, session, site="local"): + """ Returns save directory as a String for the analysis report """ + # Getting the directory where the analysis report should be saved analysis_path = os.path.join(os.path.expanduser("~/"),'.eegnb', 'analysis') report_path = os.path.join(analysis_path, experiment, site, eegdevice, "subject{}".format(subject), "session{}".format(session)) + + # Creating the directory if it doesn't exist if not os.path.isdir(report_path): os.makedirs(report_path) - return report_path - #return os.path.join(report_path, "analysis_report_{}".format(datetime.now().strftime("%d/%m/%Y_%H-%M-%S"))) \ No newline at end of file + + return report_path \ No newline at end of file diff --git a/testing.py b/testing.py index 68cd0d68..fff46054 100644 --- a/testing.py +++ b/testing.py @@ -7,6 +7,6 @@ file_path = r"C:\Users\Parv\.eegnb\data\visual-N170\local\muse2\subject0001\session004\recording_2022-08-15-19.09.37.csv" -raw, epochs = load_eeg_data(experiment='visual-N170', subject=1, session=4, device_name='muse2', example=False) +raw, epochs = load_eeg_data(experiment='visual-N170', subject=1, session=3, device_name='muse2', example=False) make_erp_plot(epochs) From 41a6563e39cd590f86abb2eab6cc35a12aaf8ee8 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Sun, 28 Aug 2022 21:47:50 +0100 Subject: [PATCH 14/35] Almost through --- eegnb/analysis/analysis_report.py | 38 ++++---------------- eegnb/analysis/pipelines.py | 58 +++++++++---------------------- 2 files changed, 23 insertions(+), 73 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 6220e24e..7b95283b 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -9,6 +9,12 @@ pdf.add_page() # Do whatever you want to add to the pdf pdf.save_as_report() + + +# Work that needs to be done +1. Add boilerplate text +2. Figure stretching for raw plot +3. Add subject and session info """ from fpdf import FPDF @@ -28,7 +34,7 @@ def header(self): self.cell(0, 10, 'Analysis Report') # Line break self.ln(20) - + # Page footer def footer(self): # Position at 1.5 cm from bottom @@ -50,33 +56,3 @@ def add_figure(self, fig_path, x, y, w, h, title): # Delete figure from memory os.remove(fig_path) - def add_matplotlib_figure(self, fig_path): - - # Add figure to document - self.image(fig_path, x=60, y=200, w=100, h=100) - - # Delete figure from memory - #os.remove(fig_path) - - -if __name__ == "__main__": - - # Instantiation of inherited class - pdf = PDF() - pdf.alias_nb_pages() - pdf.add_page() - pdf.imagex() - # Create random figure with matplotlib - fig = plt.figure() - ax = fig.add_subplot(111) - ax.plot(np.random.rand(100), '-') - ax.set_title('Random Plot') - ax.set_xlabel('x') - ax.set_ylabel('y') - ax.grid(True) - - # Convert figure to pdf page - plt.savefig('books_read.png') - - pdf.add_matplotlib_figure('books_read.png') - pdf.output('tuto2.pdf', 'F') diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index a069cdc2..349b7ded 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -3,11 +3,11 @@ CLI Pipeline for Analysis of EEGNB Recorded Data To do: -1. Package parameters into a dictionary -2. Adapt for other experiments/create an easy user manual -3. Add additional analysis methods +1. Beautify analysis pdf +2. Handle cli automated errors for report creation +3. Implement interface to erp plot function for cli -Usage (for downloaded datasets): +Usage (for downloaded datasets) -> automatically creates analysis report : Visual N170: raw, epochs = load_eeg_data('visual-n170', device_name='muse2016_bfn') @@ -24,18 +24,6 @@ raw, epochs = load_eeg_data(experiment, subject, session, device_name, tmin, tmax, reject) make_erp_plot(epochs, title) -Changable parameters to adjust plot - -tmin, tmax - Start and end time of the epochs in seconds, relative to the time-locked event. -The closest or matching samples corresponding to the start and end time are included. - -reject - Reject epochs based on maximum peak-to-peak signal amplitude (PTP), -i.e. the absolute difference between the lowest and the highest signal value. - -ci - confidence interval - -n_boot - number of bootstrap samples - """ # Some standard pythonic imports @@ -44,6 +32,7 @@ import warnings import matplotlib.pyplot as plt from datetime import datetime +import numpy as np warnings.filterwarnings('ignore') @@ -189,13 +178,8 @@ def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n diff_waveform=None, #(1, 2)) channel_order=[1,0,2,3]) # reordering of epochs.ch_names according to [[0,2],[1,3]] of subplot axes - # Convert to automatic by searching the max and min values of the ERP - """ - Axis scaling needs to be sorted out - """ - #for i in [0,2]: ax[i].set_ylim([-0.5,0.5]) - #for i in [1,3]: ax[i].set_ylim([-1.5,2.5]) - plt.autoscale() + # Autoscaling the y axis to a tight fit to the ERP + for i in [0,1,2,3]: ax[i].autoscale(tight=True) # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. plt.savefig("erp_plot.png") @@ -239,24 +223,8 @@ def create_pdf(): # Saving report pdf.output(os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))), 'F') -def create_analysis_report(experiment, eegdevice, data_path=None, bluemuse_file_fix=False): - - # Fault check if the data path is not specified, alternatively could make it a necessary parameter - if not data_path: - print("Could not find file!") - return - - # Fixing the muse2 recording issue if it was recorded using bluemuse - if bluemuse_file_fix: - fix_musemissinglines(data_path) - - # Loading the data - raw, epochs = load_eeg_data(experiment=experiment, device_name=eegdevice, fnames=data_path, example=False) - make_erp_plot(epochs) - - # Creating the analysis report, called automatically when erp plot is made - #create_pdf() - + # Informing the user that the report has been saved + print('Analysis report saved to {}'.format(save_dir)) def get_analysis_save_directory(experiment, eegdevice, subject, session, site="local"): """ Returns save directory as a String for the analysis report """ @@ -269,4 +237,10 @@ def get_analysis_save_directory(experiment, eegdevice, subject, session, site="l if not os.path.isdir(report_path): os.makedirs(report_path) - return report_path \ No newline at end of file + return report_path + +def create_analysis_report(experiment, eegdevice, data_path=None, bluemuse_file_fix=False): + """ Interface with the erp plot function, basically cli type instructions """ + + # Prompt user to enter options and then take inputs and do the necessary + pass From 95d09c81676b53cd6c87112b71edffc068edd5d2 Mon Sep 17 00:00:00 2001 From: Parv Agarwal <65726543+Parvfect@users.noreply.github.com> Date: Tue, 30 Aug 2022 08:43:06 +0100 Subject: [PATCH 15/35] Update eegnb/cli/__main__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Erik Bjäreholt --- eegnb/cli/__main__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index e60bac4e..6149d68a 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -79,9 +79,7 @@ def askforsigqualcheck(): askforsigqualcheck() def askforreportcheck(): - do_sigqual = input("\n\nGenerate Report? (y/n). Recommend y \n") - if do_sigqual != 'y': - generatereport= False + generatereport = input("\n\nGenerate Report? (Y/n): ").lower() != "n" if dosigqualcheck: askforsigqualcheck() From a2d09c7b3134322ee401c9e2c22dde0618c8b1db Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 30 Aug 2022 14:55:46 +0100 Subject: [PATCH 16/35] Trying to create cli but it's being really painful --- eegnb/analysis/pipelines.py | 5 +++-- eegnb/cli/__main__.py | 30 +++++++++++++++++++++++++++--- eegnb/cli/introprompt.py | 23 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 349b7ded..2bc37309 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -239,8 +239,9 @@ def get_analysis_save_directory(experiment, eegdevice, subject, session, site="l return report_path -def create_analysis_report(experiment, eegdevice, data_path=None, bluemuse_file_fix=False): +def create_analysis_report(experiment, eegdevice, subject=None, session=None, data_path=None, bluemuse_file_fix=False): """ Interface with the erp plot function, basically cli type instructions """ # Prompt user to enter options and then take inputs and do the necessary - pass + raw, epochs = load_eeg_data(experiment=experiment, subject=subject, session=session, device_name=eegdevice, example=False, fnames=data_path) + make_erp_plot(epochs) diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index e60bac4e..b9e599ad 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -6,12 +6,12 @@ import shutil from eegnb.datasets.datasets import zip_data_folders -from .introprompt import intro_prompt +from .introprompt import intro_prompt, analysis_intro_prompt from .utils import run_experiment from eegnb import generate_save_fn from eegnb.devices.eeg import EEG from eegnb.analysis.utils import check_report -from eegnb.analysis.pipelines import create_analysis_report +from eegnb.analysis.pipelines import load_eeg_data, make_erp_plot, create_analysis_report @click.group(name="eegnb") @@ -97,7 +97,29 @@ def askforreportcheck(): create_analysis_report(experiment, eegdevice, outfname) - +@main.command() +@click.option("-ex", "--experiment", help="Experiment to run") +@click.option("-ed", "--eegdevice", help="EEG device to use") +@click.option("-sub", "--subject", help="Subject ID") +@click.option("-sess", "--session", help="Session number") +@click.option( + "-ip", "--prompt", help="Use interactive prompt to ask for parameters", is_flag=True +) +def create_analysis_report( + experiment: str, + eegdevice: str = None, + subject: str = None, + session: str = None, + prompt: bool = False, + filepath:str = None +): + """ + Create analysis report of recorded data + """ + if prompt: + eegdevice, experiment, subject, session, filepath = analysis_intro_prompt() + create_analysis_report(experiment, eegdevice, subject, session, filepath) + return @main.command() @click.option("-ed", "--eegdevice", help="EEG device to use", required=True) @@ -122,6 +144,8 @@ def checksigqual(eegdevice: str): # valuess in the function definition ] + + @main.command() @click.option("-ex", "--experiment", help="Experiment to zip", required=False) @click.option( diff --git a/eegnb/cli/introprompt.py b/eegnb/cli/introprompt.py index 5061bbc3..7c2f48b6 100644 --- a/eegnb/cli/introprompt.py +++ b/eegnb/cli/introprompt.py @@ -157,6 +157,29 @@ def intro_prompt() -> Tuple[EEG, str, int, str]: return eeg_device, exp_selection, duration, str(save_fn) +def analysis_intro_prompt(): + + # check if user has filepath + print("Welcome to NeurotechX EEG Notebooks\n") + print("Do you have a filepath to a .csv file you would like to analyze? \n") + print("[1] Yes \n") + print("[0] No \n") + file_idx = int(input("Enter selection: ")) + if file_idx == 1: + print("Please enter the filepath to the .csv file you would like to analyze. \n") + file_path = input("Enter filepath: ") + subject, session = None, None + else: + subject = input("Enter subject ID#: ") + session = input("Enter session #: ") + file_path = None + + eegdevice = input("Enter EEG device: ") + exp_selection = exp_prompt() + + return eegdevice, exp_selection, subject, session, file_path + + def intro_prompt_zip() -> Tuple[str,str]: """This function handles the user prompts for inputting information for zipping their function.""" From 7f1a0dab0c08fcc979b265816fc5deab24f61767 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 30 Aug 2022 15:00:12 +0100 Subject: [PATCH 17/35] Extra word cli error --- eegnb/cli/introprompt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eegnb/cli/introprompt.py b/eegnb/cli/introprompt.py index 7c2f48b6..425f2bc0 100644 --- a/eegnb/cli/introprompt.py +++ b/eegnb/cli/introprompt.py @@ -167,17 +167,17 @@ def analysis_intro_prompt(): file_idx = int(input("Enter selection: ")) if file_idx == 1: print("Please enter the filepath to the .csv file you would like to analyze. \n") - file_path = input("Enter filepath: ") + filepath = input("Enter filepath: ") subject, session = None, None else: subject = input("Enter subject ID#: ") session = input("Enter session #: ") - file_path = None + filepath = None eegdevice = input("Enter EEG device: ") - exp_selection = exp_prompt() + experiment = exp_prompt() - return eegdevice, exp_selection, subject, session, file_path + return experiment, eegdevice, subject, session, filepath From fe07732ed0e815713bb85c43ac80a448192de047 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Wed, 31 Aug 2022 14:18:01 +0100 Subject: [PATCH 18/35] Changed example handling --- eegnb/analysis/pipelines.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 2bc37309..dc9796c6 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -7,22 +7,17 @@ 2. Handle cli automated errors for report creation 3. Implement interface to erp plot function for cli -Usage (for downloaded datasets) -> automatically creates analysis report : +Usage: -Visual N170: -raw, epochs = load_eeg_data('visual-n170', device_name='muse2016_bfn') -make_erp_plot(epochs) +For Recorded Data: -Visual P300: -raw, epochs = load_eeg_data('visual-P300', device_name='muse2016', event_id={'Non-Target': 1, 'Target': 2}) -make_erp_plot(epochs, conditions=OrderedDict(NonTarget=[1],Target=[2])) +from eegnb.analysis.pipelines import create_analysis_report() +create_analysis_report(experiment, eegdevice, subject, session, filepath)s -Other Experiments to be added later eg. Visual SSVEP +For Example Datasets: -Loading Recorded Data : - -raw, epochs = load_eeg_data(experiment, subject, session, device_name, tmin, tmax, reject) -make_erp_plot(epochs, title) +from eegnb.analysis.pipelines import example_analysis_report() +example_analysis_report() """ @@ -53,7 +48,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', tmin=-0.1, tmax=0.6, baseline=None, reject={'eeg': 5e-5}, preload=True, verbose=1, - picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2), fnames=None, example=True): + picks=[0,1,2,3], event_id = OrderedDict(House=1,Face=2), fnames=None, example=False): """ Loads EEG data from the specified experiment, subject, session, and device. Returns the raw and epochs objects. @@ -112,7 +107,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # If using the example dataset, load the data from the example dataset else: - subject_id, session_nb = subject, session + subject_id, session_nb = 1, 1 # Loading Data eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') @@ -122,7 +117,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', if not os.path.isdir(experiment_data_path): fetch_dataset(data_dir=eegnb_data_path, experiment=experiment, site='eegnb_examples') - raw = load_data(subject,session, + raw = load_data(1,1, experiment=experiment, site='eegnb_examples', device_name=device_name, data_dir = eegnb_data_path) @@ -245,3 +240,15 @@ def create_analysis_report(experiment, eegdevice, subject=None, session=None, da # Prompt user to enter options and then take inputs and do the necessary raw, epochs = load_eeg_data(experiment=experiment, subject=subject, session=session, device_name=eegdevice, example=False, fnames=data_path) make_erp_plot(epochs) + +def example_analysis_report(): + """ Example of how to use the analysis report function """ + experiment = ["visual-N170", "visual-P300"] + experiment_choice = experiment[int(input("Choose an experiment: {} 0 or 1".format(experiment)))] + + if experiment_choice == "visual-N170": + raw, epochs = load_eeg_data(experiment_choice, example=True) + make_erp_plot(epochs) + else: + raw, epochs = load_eeg_data('visual-P300', device_name='muse2016', event_id={'Non-Target': 1, 'Target': 2}, example=True) + make_erp_plot(epochs, conditions=OrderedDict(NonTarget=[1],Target=[2])) \ No newline at end of file From f703fbd8ee4ccd9af28452912078955504434de3 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 1 Sep 2022 12:02:20 +0100 Subject: [PATCH 19/35] Pain --- eegnb/cli/__main__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index 6550bd27..c22793c3 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -100,6 +100,7 @@ def askforreportcheck(): @click.option("-ed", "--eegdevice", help="EEG device to use") @click.option("-sub", "--subject", help="Subject ID") @click.option("-sess", "--session", help="Session number") +@click.option("-fp", "--filepath", help="Filepath to save data") @click.option( "-ip", "--prompt", help="Use interactive prompt to ask for parameters", is_flag=True ) @@ -108,14 +109,14 @@ def create_analysis_report( eegdevice: str = None, subject: str = None, session: str = None, + filepath:str = None, prompt: bool = False, - filepath:str = None ): """ Create analysis report of recorded data """ if prompt: - eegdevice, experiment, subject, session, filepath = analysis_intro_prompt() + experiment, eegdevice, subject, session, filepath = analysis_intro_prompt() create_analysis_report(experiment, eegdevice, subject, session, filepath) return From d8976f3b0c4769e6c0347ea197df6fe1405362f7 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 1 Sep 2022 12:07:12 +0100 Subject: [PATCH 20/35] Adding whole datapath --- .ipynb_checkpoints/Untitled-checkpoint.ipynb | 6 ------ eegnb/analysis/pipelines.py | 7 +++++-- 2 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 .ipynb_checkpoints/Untitled-checkpoint.ipynb diff --git a/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/.ipynb_checkpoints/Untitled-checkpoint.ipynb deleted file mode 100644 index 363fcab7..00000000 --- a/.ipynb_checkpoints/Untitled-checkpoint.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index dc9796c6..e04ec66f 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -215,11 +215,14 @@ def create_pdf(): # Getting the directory where the report should be saved save_dir = get_analysis_save_directory(experiment=experiment_name, eegdevice=eegdevice, subject=subject_id, session=session_nb) + #get whole filepath + filepath = os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))) + # Saving report - pdf.output(os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))), 'F') + pdf.output(filepath, 'F') # Informing the user that the report has been saved - print('Analysis report saved to {}'.format(save_dir)) + print('Analysis report saved to {}'.format(filepath)) def get_analysis_save_directory(experiment, eegdevice, subject, session, site="local"): """ Returns save directory as a String for the analysis report """ From cbe6a132caacc48b86b702e0e1124575409117ee Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 1 Sep 2022 19:59:26 +0100 Subject: [PATCH 21/35] Finally fixed cli --- eegnb/analysis/pipelines.py | 16 +++++++++++----- eegnb/cli/__main__.py | 14 ++++++++++---- eegnb/cli/introprompt.py | 4 ++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index e04ec66f..2a6fb492 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -44,7 +44,7 @@ from pathlib import Path DATA_DIR = os.path.join(os.path.expanduser("~/"), ".eegnb", "data") -eegdevice, experiment_name, subject_id, session_nb = None, None, None, None +eegdevice, experiment_name, subject_id, session_nb, example_flag = None, None, None, None, False def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', tmin=-0.1, tmax=0.6, baseline=None, reject={'eeg': 5e-5}, preload=True, verbose=1, @@ -77,7 +77,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', """ # Parameters that are used in creating the save directory for the analysis report and hence global variables - global eegdevice, experiment_name, subject_id, session_nb + global eegdevice, experiment_name, subject_id, session_nb, example_flag # Set the global variables based on the parameter values eegdevice, experiment_name = device_name, experiment @@ -108,6 +108,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # If using the example dataset, load the data from the example dataset else: subject_id, session_nb = 1, 1 + example_flag = True # Loading Data eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') @@ -213,7 +214,7 @@ def create_pdf(): pdf.add_figure(os.getcwd()+'\\erp_plot.png', x=10, y=160, w=200, h=120, title="ERP Plot") # Getting the directory where the report should be saved - save_dir = get_analysis_save_directory(experiment=experiment_name, eegdevice=eegdevice, subject=subject_id, session=session_nb) + save_dir = get_analysis_save_directory(experiment=experiment_name, eegdevice=eegdevice, subject=subject_id, session=session_nb, example=example_flag) #get whole filepath filepath = os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))) @@ -224,9 +225,14 @@ def create_pdf(): # Informing the user that the report has been saved print('Analysis report saved to {}'.format(filepath)) -def get_analysis_save_directory(experiment, eegdevice, subject, session, site="local"): +def get_analysis_save_directory(experiment, eegdevice, subject, session, example): """ Returns save directory as a String for the analysis report """ + if not example: + site='local' + else: + site='eegnb_examples' + # Getting the directory where the analysis report should be saved analysis_path = os.path.join(os.path.expanduser("~/"),'.eegnb', 'analysis') report_path = os.path.join(analysis_path, experiment, site, eegdevice, "subject{}".format(subject), "session{}".format(session)) @@ -237,7 +243,7 @@ def get_analysis_save_directory(experiment, eegdevice, subject, session, site="l return report_path -def create_analysis_report(experiment, eegdevice, subject=None, session=None, data_path=None, bluemuse_file_fix=False): +def create_analysis_report_(experiment, eegdevice, subject=None, session=None, data_path=None, bluemuse_file_fix=False): """ Interface with the erp plot function, basically cli type instructions """ # Prompt user to enter options and then take inputs and do the necessary diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index c22793c3..f54de338 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -11,7 +11,7 @@ from eegnb import generate_save_fn from eegnb.devices.eeg import EEG from eegnb.analysis.utils import check_report -from eegnb.analysis.pipelines import load_eeg_data, make_erp_plot, create_analysis_report +from eegnb.analysis.pipelines import load_eeg_data, make_erp_plot, create_analysis_report_, example_analysis_report @click.group(name="eegnb") @@ -115,10 +115,16 @@ def create_analysis_report( """ Create analysis report of recorded data """ + if prompt: - experiment, eegdevice, subject, session, filepath = analysis_intro_prompt() - create_analysis_report(experiment, eegdevice, subject, session, filepath) - return + example = input("Do you want to load an example experiment? (y/n)") + if example == 'y': + example_analysis_report() + return + else: + experiment, eegdevice, subject, session, filepath = analysis_intro_prompt() + create_analysis_report_(experiment, eegdevice, subject, session, filepath) + @main.command() @click.option("-ed", "--eegdevice", help="EEG device to use", required=True) diff --git a/eegnb/cli/introprompt.py b/eegnb/cli/introprompt.py index 425f2bc0..66e80f72 100644 --- a/eegnb/cli/introprompt.py +++ b/eegnb/cli/introprompt.py @@ -170,8 +170,8 @@ def analysis_intro_prompt(): filepath = input("Enter filepath: ") subject, session = None, None else: - subject = input("Enter subject ID#: ") - session = input("Enter session #: ") + subject = int(input("Enter subject ID#: ")) + session = int(input("Enter session #: ")) filepath = None eegdevice = input("Enter EEG device: ") From a339ffc8a263044352bf65d4f24c98aca06089c7 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 5 Sep 2022 02:33:31 +0100 Subject: [PATCH 22/35] hmm --- eegnb/analysis/pipelines.py | 2 ++ requirements.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 2a6fb492..a8f0c136 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -177,6 +177,8 @@ def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n # Autoscaling the y axis to a tight fit to the ERP for i in [0,1,2,3]: ax[i].autoscale(tight=True) + # for a in ax.ravel (): a.autoscale(tight=True) + # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. plt.savefig("erp_plot.png") plt.show() diff --git a/requirements.txt b/requirements.txt index 062ac0fb..7f61db4c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ pytest-shutil pyo>=1.0.3; platform_system == "Linux" keyboard==0.13.5 + # This might try to build from source on linux (since there are no wheels for Linux on PyPI) . # You can pass `--find-links=https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04/` your `pip install` to use the prebuilt wheels at the link. wxPython>=4.0 ; platform_system == "Linux" From dd6fda05838f129219945a4dfb800645fafda9eb Mon Sep 17 00:00:00 2001 From: Parvfect Date: Wed, 21 Sep 2022 14:37:51 +0100 Subject: [PATCH 23/35] Looking good --- eegnb/analysis/pipelines.py | 44 ++++++++++++++++++------------------- eegnb/cli/__main__.py | 1 + eegnb/cli/introprompt.py | 36 +++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index a8f0c136..21d35cae 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -28,6 +28,7 @@ import matplotlib.pyplot as plt from datetime import datetime import numpy as np +from typing import Dict warnings.filterwarnings('ignore') @@ -76,12 +77,6 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', fnames : File names of the experiment data, if not passed, example files are used """ - # Parameters that are used in creating the save directory for the analysis report and hence global variables - global eegdevice, experiment_name, subject_id, session_nb, example_flag - - # Set the global variables based on the parameter values - eegdevice, experiment_name = device_name, experiment - # If not using the example dataset, load the data from the specified experiment using load_csv_as_raw if not example: @@ -92,8 +87,6 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # Generate file names if not passed if fnames is None: raw = load_data(subject_id=subject, session_nb=session, experiment=experiment, device_name=device_name, site="local", data_dir=os.path.join(os.path.expanduser('~/'),'.eegnb', 'data')) - # Setting the session id and session number for the analysis report - subject_id, session_nb = subject, session else: # Replace Ch names has arbitarily been set to None @@ -103,13 +96,12 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', raw = load_csv_as_raw([fnames], sfreq=sfreq, ch_ind=ch_ind, replace_ch_names=None, verbose=verbose) # Getting the subject and session - subject_id, session_nb = fnames.split('_')[1], fnames.split('_')[2] + subject, session = fnames.split('_')[1], fnames.split('_')[2] # If using the example dataset, load the data from the example dataset else: - subject_id, session_nb = 1, 1 - example_flag = True - + subject, session = 1, 1 + # Loading Data eegnb_data_path = os.path.join(os.path.expanduser('~/'),'.eegnb', 'data') experiment_data_path = os.path.join(eegnb_data_path, experiment, 'eegnb_examples') @@ -149,10 +141,12 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) print(epochs) - return raw, epochs + experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example} + return epochs, experimental_parameters -def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n_boot=1000, title='', + +def make_erp_plot(epochs, experimental_parameters:Dict, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n_boot=1000, title='', diff_waveform=None, #(1, 2)) channel_order=[1,0,2,3]): """ @@ -184,11 +178,14 @@ def make_erp_plot(epochs, conditions=OrderedDict(House=[1],Face=[2]), ci=97.5, n plt.show() # Creating the pdf, needs to be discussed whether we want to call it here or seperately. - create_pdf() + create_pdf(experimental_parameters) -def create_pdf(): +def create_pdf(experimental_parameters:Dict): """Creates analysis report using the power spectrum and ERP plots that are saved in the directory""" + # Unpack the experimental parameters + eegdevice, experiment, subject, session, example = experimental_parameters.values() + # Initialzing the pdf pdf = PDF() pdf.alias_nb_pages() @@ -216,7 +213,7 @@ def create_pdf(): pdf.add_figure(os.getcwd()+'\\erp_plot.png', x=10, y=160, w=200, h=120, title="ERP Plot") # Getting the directory where the report should be saved - save_dir = get_analysis_save_directory(experiment=experiment_name, eegdevice=eegdevice, subject=subject_id, session=session_nb, example=example_flag) + save_dir = get_analysis_save_directory(experiment=experiment, eegdevice=eegdevice, subject=subject, session=session, example=example) #get whole filepath filepath = os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))) @@ -226,6 +223,7 @@ def create_pdf(): # Informing the user that the report has been saved print('Analysis report saved to {}'.format(filepath)) + print("Open as pdf by clicking the following link: file://{}".format(filepath)) def get_analysis_save_directory(experiment, eegdevice, subject, session, example): """ Returns save directory as a String for the analysis report """ @@ -249,8 +247,8 @@ def create_analysis_report_(experiment, eegdevice, subject=None, session=None, d """ Interface with the erp plot function, basically cli type instructions """ # Prompt user to enter options and then take inputs and do the necessary - raw, epochs = load_eeg_data(experiment=experiment, subject=subject, session=session, device_name=eegdevice, example=False, fnames=data_path) - make_erp_plot(epochs) + epochs, experimental_parameters = load_eeg_data(experiment=experiment, subject=subject, session=session, device_name=eegdevice, example=False, fnames=data_path) + make_erp_plot(epochs, experimental_parameters) def example_analysis_report(): """ Example of how to use the analysis report function """ @@ -258,8 +256,8 @@ def example_analysis_report(): experiment_choice = experiment[int(input("Choose an experiment: {} 0 or 1".format(experiment)))] if experiment_choice == "visual-N170": - raw, epochs = load_eeg_data(experiment_choice, example=True) - make_erp_plot(epochs) + epochs, experimental_parameters = load_eeg_data(experiment_choice, example=True) + make_erp_plot(epochs, experimental_parameters) else: - raw, epochs = load_eeg_data('visual-P300', device_name='muse2016', event_id={'Non-Target': 1, 'Target': 2}, example=True) - make_erp_plot(epochs, conditions=OrderedDict(NonTarget=[1],Target=[2])) \ No newline at end of file + epochs, experimental_parameters = load_eeg_data('visual-P300', device_name='muse2016', event_id={'Non-Target': 1, 'Target': 2}, example=True) + make_erp_plot(epochs, experimental_parameters, conditions=OrderedDict(NonTarget=[1],Target=[2])) \ No newline at end of file diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index f54de338..7d0ad31c 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -118,6 +118,7 @@ def create_analysis_report( if prompt: example = input("Do you want to load an example experiment? (y/n)") + print() if example == 'y': example_analysis_report() return diff --git a/eegnb/cli/introprompt.py b/eegnb/cli/introprompt.py index 66e80f72..8620216a 100644 --- a/eegnb/cli/introprompt.py +++ b/eegnb/cli/introprompt.py @@ -157,6 +157,40 @@ def intro_prompt() -> Tuple[EEG, str, int, str]: return eeg_device, exp_selection, duration, str(save_fn) +def analysis_device_prompt(): + + boards = { + "none": "None", + "muse2016": "Muse (2016)", + "muse2": "Muse 2", + "museS": "Muse S", + "muse2016_bfn": "Muse 2016 - brainflow, native bluetooth", + "muse2016_bfb": "Muse 2016 - brainflow, BLED bluetooth dongle", + "muse2_bfn": "Muse 2 - brainflow, native bluetooth", + "muse2_bfb": "Muse 2 - brainflow, BLED bluetooth dongle", + "museS_bfn": "Muse S - brainflow, native bluetooth", + "museS_bfb": "Muse S - brainflow, BLED bluetooth dongle", + "ganglion": "OpenBCI Ganglion", + "cyton": "OpenBCI Cyton", + "cyton_daisy": "OpenBCI Cyton + Daisy", + "unicorn": "G.Tec Unicorn", + "brainbit": "BrainBit", + "notion1": "Notion 1", + "notion2": "Notion 2", + "crown": "Crown", + "synthetic": "Synthetic", + "freeeeg32": "FreeEEG32", + } + + print("Please enter the integer value corresponding to your EEG device: \n") + print("\n".join(f"[{i:2}] {board}" for i, board in enumerate(boards.values()))) + + board_idx = int(input("\nEnter Board Selection: ")) + + # Board_codes are the actual names to be passed to the EEG class + board_code = list(boards.keys())[board_idx] + return board_code + def analysis_intro_prompt(): # check if user has filepath @@ -174,7 +208,7 @@ def analysis_intro_prompt(): session = int(input("Enter session #: ")) filepath = None - eegdevice = input("Enter EEG device: ") + eegdevice = analysis_device_prompt() experiment = exp_prompt() return experiment, eegdevice, subject, session, filepath From 2be3e62ef88c57765622093cfa6bb5b4a17076f9 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Wed, 21 Sep 2022 14:51:58 +0100 Subject: [PATCH 24/35] added hyperlink --- eegnb/analysis/pipelines.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 21d35cae..8ee1397e 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -126,7 +126,9 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. fig.savefig("power_spectrum.png") - plt.show() + plt.show(block=False) + plt.pause(10) + plt.close() # Epoching # Create an array containing the timestamps and type of each stimulus (i.e. face or house) @@ -175,7 +177,9 @@ def make_erp_plot(epochs, experimental_parameters:Dict, conditions=OrderedDict(H # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. plt.savefig("erp_plot.png") - plt.show() + plt.show(block=False) + plt.pause(10) + plt.close() # Creating the pdf, needs to be discussed whether we want to call it here or seperately. create_pdf(experimental_parameters) @@ -222,8 +226,8 @@ def create_pdf(experimental_parameters:Dict): pdf.output(filepath, 'F') # Informing the user that the report has been saved - print('Analysis report saved to {}'.format(filepath)) - print("Open as pdf by clicking the following link: file://{}".format(filepath)) + print('Analysis report saved to {}\n'.format(filepath)) + print("Open as pdf by clicking the following link: {}{}".format("file:///", filepath)) def get_analysis_save_directory(experiment, eegdevice, subject, session, example): """ Returns save directory as a String for the analysis report """ From 54c67cb340988e20f2bbcf79030d731fa7a9608b Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 22 Sep 2022 12:40:56 +0100 Subject: [PATCH 25/35] Having some issues with detecting css and image deltetion --- eegnb/analysis/analysis_report.py | 112 ++++++++++++++++-------------- eegnb/analysis/report.html | 60 ++++++++++++++++ eegnb/analysis/styling.css | 34 +++++++++ 3 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 eegnb/analysis/report.html create mode 100644 eegnb/analysis/styling.css diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 7b95283b..656dd485 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -1,58 +1,68 @@ -""" -File that has the functions to generate the analysis report pdf from the images -Usage Instructions typically :: -from eegnb.analysis.analysis_report import PDF -pdf = PDF() -pdf.alias_nb_pages() -pdf.add_page() -# Do whatever you want to add to the pdf -pdf.save_as_report() +# Generating html using Python +from airium import Airium +from typing import Dict +import os +a = Airium() -# Work that needs to be done -1. Add boilerplate text -2. Figure stretching for raw plot -3. Add subject and session info -""" -from fpdf import FPDF -import matplotlib.pyplot as plt -import numpy as np -import os +def get_html(experimental_parameters: Dict): + + eeg_device, experiment, subject, session, example, drop_percentage = experimental_parameters.values() + #experiment_text = "" + #with open('experiment_descriptions/{}.txt'.format(experiment), 'r') as f: + # experiment_text = f.readlines() + + a('') + with a.html(): + with a.head(): + a.link(href=os.getcwd()+"\\styling.css", rel='stylesheet', type="text/css") + a.title(_t="Analysis Report") + + with a.body(): + + # Navigation bar + with a.div(klass="topnav"): + a.a(_t="Description", href="#Description") + a.a(_t="Raw Epoch", href="#Raw Epoch") + a.a(_t="Stimulus Response", href="#Stimulus Response") + a.a(_t="About", href="#about") + + # Description + with a.div(id="Description"): + a.h1(_t="Analysis Report") + with a.p(): + a("Experiment Name: {}
".format(experiment)) + + if example: + a("Example File
") + else: + a("Subject Id: {}
".format(subject)) + a("Session Id: {}
".format(session)) + + a("EEG Device: {}
".format(eeg_device)) + a("Drop Percentage: {} %

".format(round(drop_percentage,2))) + a('This is an analysis report for the experiment.
For more information about the experiment, please visit the documentation') + #a(experiment_text) + + # Raw Epoch + with a.div(id="Raw Epoch"): + a.h2(_t="Raw Epoch") + with a.p(): + a("The raw epoch is shown below. The raw epoch is the data that is recorded from the EEG headset. The raw epoch is then processed to remove noise and artifacts.") + a.img(src="power_spectrum.png", alt="Raw Epoch") + + # Stimulus Response + with a.div(id="Stimulus Response"): + a.h2(_t="Stimulus Response") + with a.p(): + a("The stimulus response is shown below. The stimulus response is the data that is recorded from the EEG headset after removing noise and artifacts.") + a.img(src="erp_plot.png", alt="Stimulus Response") -class PDF(FPDF): - def header(self): - - # Arial bold 15 - self.set_font('Arial', 'B', 25) - self.set_text_color(183, 208, 332) - # Move to the right - #self.cell(80) - # Title - self.cell(0, 10, 'Analysis Report') - # Line break - self.ln(20) - - # Page footer - def footer(self): - # Position at 1.5 cm from bottom - self.set_y(-15) - # Arial italic 8 - self.set_font('Arial', 'I', 8) - # Page number - self.cell(0, 10, 'Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C') - - def add_figure(self, fig_path, x, y, w, h, title): - - #self.cell(w=100,h=10, txt=title, ln=0, align='C') - self.set_font('Times', 'B', 20) - - # Add figure to document - self.cell(0, 10, txt=title, ln=2, align='C') - self.image(fig_path, x=x, y=y, w=w, h=h) - self.ln(h+10) - # Delete figure from memory - os.remove(fig_path) + # Delete saved pictures + os.remove("power_spectrum.png") + os.remove("erp_plot.png") + return str(a) diff --git a/eegnb/analysis/report.html b/eegnb/analysis/report.html new file mode 100644 index 00000000..b0f91f77 --- /dev/null +++ b/eegnb/analysis/report.html @@ -0,0 +1,60 @@ + + + + + + + Analysis Report + + + + + + +
+

+ Analysis Report +

+

+ Experiment Name:
+ Subject Id:
+ Session Number:
+ EEG Headset:
+ Drop Percentage:
+

+ +

+ This is an analysis report for the experiment.
+ For more information about the experiment, please visit the documentation. +

+
+ + +
+

+ Raw Epoch +

+

+ The raw epoch is shown below. The raw epoch is the data that is recorded from the EEG headset. The raw epoch is then processed to remove noise and artifacts. +

+ Raw Epoch +
+ + +
+

+ Stimulus Response +

+

+ The stimulus response is shown below. The stimulus response is the data that is recorded from the EEG headset after the raw epoch has been processed. The stimulus response is then used to calculate the power spectrum. +

+ Stimulus Response +
+ + + diff --git a/eegnb/analysis/styling.css b/eegnb/analysis/styling.css new file mode 100644 index 00000000..4b000843 --- /dev/null +++ b/eegnb/analysis/styling.css @@ -0,0 +1,34 @@ +/* Add a black background color to the top navigation */ +.topnav { + background-color: #333; + overflow: hidden; + } + + /* Style the links inside the navigation bar */ + .topnav a { + float: left; + color: #f2f2f2; + text-align: center; + padding: 14px 16px; + text-decoration: none; + font-size: 17px; + } + + /* Change the color of links on hover */ + .topnav a:hover { + background-color: #ddd; + color: black; + } + + /* Add a color to the active/current link */ + .topnav a.active { + background-color: #04AA6D; + color: white; + } + + /* Centre the images */ + img { + display: block; + margin-left: auto; + margin-right: auto; + } \ No newline at end of file From 957b8777a9afd009c6c1d4a824bda348a7d69ce1 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 26 Sep 2022 12:29:03 +0100 Subject: [PATCH 26/35] Just the css now --- eegnb/analysis/analysis_report.py | 17 +++----- eegnb/analysis/pipelines.py | 70 ++++++++++++------------------- requirements.txt | 1 + 3 files changed, 33 insertions(+), 55 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 656dd485..dbb519b7 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -1,5 +1,4 @@ - # Generating html using Python from airium import Airium @@ -7,10 +6,9 @@ import os a = Airium() - def get_html(experimental_parameters: Dict): - - eeg_device, experiment, subject, session, example, drop_percentage = experimental_parameters.values() + print(os.getcwd()) + eeg_device, experiment, subject, session, example, drop_percentage, image_save_path = experimental_parameters.values() #experiment_text = "" #with open('experiment_descriptions/{}.txt'.format(experiment), 'r') as f: # experiment_text = f.readlines() @@ -18,7 +16,7 @@ def get_html(experimental_parameters: Dict): a('') with a.html(): with a.head(): - a.link(href=os.getcwd()+"\\styling.css", rel='stylesheet', type="text/css") + a.link(href="styling.css", rel='stylesheet', type="text/css") a.title(_t="Analysis Report") with a.body(): @@ -28,7 +26,6 @@ def get_html(experimental_parameters: Dict): a.a(_t="Description", href="#Description") a.a(_t="Raw Epoch", href="#Raw Epoch") a.a(_t="Stimulus Response", href="#Stimulus Response") - a.a(_t="About", href="#about") # Description with a.div(id="Description"): @@ -52,17 +49,13 @@ def get_html(experimental_parameters: Dict): a.h2(_t="Raw Epoch") with a.p(): a("The raw epoch is shown below. The raw epoch is the data that is recorded from the EEG headset. The raw epoch is then processed to remove noise and artifacts.") - a.img(src="power_spectrum.png", alt="Raw Epoch") + a.img(src="{}\\power_spectrum.png".format(image_save_path), alt="Raw Epoch") # Stimulus Response with a.div(id="Stimulus Response"): a.h2(_t="Stimulus Response") with a.p(): a("The stimulus response is shown below. The stimulus response is the data that is recorded from the EEG headset after removing noise and artifacts.") - a.img(src="erp_plot.png", alt="Stimulus Response") - - # Delete saved pictures - os.remove("power_spectrum.png") - os.remove("erp_plot.png") + a.img(src="{}\\erp_plot.png".format(image_save_path), alt="Stimulus Response") return str(a) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 8ee1397e..329af234 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -39,9 +39,9 @@ # EEG-Notebooks functions from eegnb import generate_save_fn from eegnb.analysis.utils import load_data,plot_conditions, load_csv_as_raw, fix_musemissinglines +from eegnb.analysis.analysis_report import get_html from eegnb.datasets import fetch_dataset from eegnb.devices.utils import EEG_INDICES, SAMPLE_FREQS -from eegnb.analysis.analysis_report import PDF from pathlib import Path DATA_DIR = os.path.join(os.path.expanduser("~/"), ".eegnb", "data") @@ -120,12 +120,15 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # Filtering the data under a certain frequency range raw.filter(1,30, method='iir') + # Configuring the image save path + image_save_path = get_save_directory(experiment=experiment, eegdevice=device_name, subject=subject, session=session, example=example, label='images') + # Visualising the power spectrum fig = raw.plot_psd(fmin=1, fmax=30, show=False) # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. - fig.savefig("power_spectrum.png") - + plt.tight_layout() + plt.savefig("{}\\power_spectrum.png".format(image_save_path)) plt.show(block=False) plt.pause(10) plt.close() @@ -143,7 +146,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) print(epochs) - experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example} + experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example, "drop_percent": (1 - len(epochs.events)/len(events)) * 100, "image_save_path":image_save_path} return epochs, experimental_parameters @@ -176,7 +179,10 @@ def make_erp_plot(epochs, experimental_parameters:Dict, conditions=OrderedDict(H # for a in ax.ravel (): a.autoscale(tight=True) # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. - plt.savefig("erp_plot.png") + + # Makes sure that the axis labels are not cut out + plt.tight_layout() + plt.savefig("{}\\erp_plot.png".format(experimental_parameters["image_save_path"])) plt.show(block=False) plt.pause(10) plt.close() @@ -188,48 +194,26 @@ def create_pdf(experimental_parameters:Dict): """Creates analysis report using the power spectrum and ERP plots that are saved in the directory""" # Unpack the experimental parameters - eegdevice, experiment, subject, session, example = experimental_parameters.values() - - # Initialzing the pdf - pdf = PDF() - pdf.alias_nb_pages() - pdf.add_page() - - """ - Note that added images to the pdf are deleted form their directory, as of now, these - images are temporarily created when plotted so they can be integrated into the pdf. - """ - - # Including experiment name, subject id and session number in the pdf - """ - pdf.set_font('Arial', 'B', 10) - pdf.cell(0, 10, 'Experiment - {}'.format(experiment_name)) - pdf.cell(0, 10, 'Subject - {}'.format(subject_id)) - pdf.cell(0, 10, 'Session - {}'.format(session_nb)) - """ + eegdevice, experiment, subject, session, example, drop_percentage, image_save_path = experimental_parameters.values() - #pdf.cell(0, 10, 'Power Spectrum', ln=1) - # Adding the Power Spectrum plot to the file - pdf.add_figure(os.getcwd()+'\\power_spectrum.png', x=10, y=40, w=180, h=100, title="Power Spectrum") - - #pdf.cell(0, 10, 'ERP', ln=1) - # Adding the ERP plot to the file - pdf.add_figure(os.getcwd()+'\\erp_plot.png', x=10, y=160, w=200, h=120, title="ERP Plot") - # Getting the directory where the report should be saved - save_dir = get_analysis_save_directory(experiment=experiment, eegdevice=eegdevice, subject=subject, session=session, example=example) + save_dir = get_save_directory(experiment=experiment, eegdevice=eegdevice, subject=subject, session=session, example=example, label="analysis") #get whole filepath - filepath = os.path.join(save_dir, 'analysis_report_{}.pdf'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))) + filepath = os.path.join(save_dir, 'analysis_report_{}.html'.format(datetime.now().strftime("%d-%m-%Y_%H-%M-%S"))) - # Saving report - pdf.output(filepath, 'F') + # Get the report + report_html = get_html(experimental_parameters) + # Save html file + with open(filepath, 'w') as f: + f.write(report_html) + # Informing the user that the report has been saved print('Analysis report saved to {}\n'.format(filepath)) print("Open as pdf by clicking the following link: {}{}".format("file:///", filepath)) -def get_analysis_save_directory(experiment, eegdevice, subject, session, example): +def get_save_directory(experiment, eegdevice, subject, session, example, label): """ Returns save directory as a String for the analysis report """ if not example: @@ -238,14 +222,14 @@ def get_analysis_save_directory(experiment, eegdevice, subject, session, example site='eegnb_examples' # Getting the directory where the analysis report should be saved - analysis_path = os.path.join(os.path.expanduser("~/"),'.eegnb', 'analysis') - report_path = os.path.join(analysis_path, experiment, site, eegdevice, "subject{}".format(subject), "session{}".format(session)) - + save_path = os.path.join(os.path.expanduser("~/"),'.eegnb', label) + save_path = os.path.join(save_path, experiment, site, eegdevice, "subject{}".format(subject), "session{}".format(session)) + # Creating the directory if it doesn't exist - if not os.path.isdir(report_path): - os.makedirs(report_path) + if not os.path.isdir(save_path): + os.makedirs(save_path) - return report_path + return save_path def create_analysis_report_(experiment, eegdevice, subject=None, session=None, data_path=None, bluemuse_file_fix=False): """ Interface with the erp plot function, basically cli type instructions """ diff --git a/requirements.txt b/requirements.txt index 7f61db4c..ee59cb25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ h5py>=3.1.0 pytest-shutil pyo>=1.0.3; platform_system == "Linux" keyboard==0.13.5 +airium>=0.1.0 # This might try to build from source on linux (since there are no wheels for Linux on PyPI) . From 67bb36e7769319d5301650a2d65936b9c81b2be5 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 26 Sep 2022 12:39:29 +0100 Subject: [PATCH 27/35] Fixed the css linking problem though it's a weird soln --- eegnb/analysis/analysis_report.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index dbb519b7..9eebd1e6 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -4,10 +4,12 @@ from airium import Airium from typing import Dict import os +import eegnb a = Airium() def get_html(experimental_parameters: Dict): - print(os.getcwd()) + + css_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis", "styling.css") eeg_device, experiment, subject, session, example, drop_percentage, image_save_path = experimental_parameters.values() #experiment_text = "" #with open('experiment_descriptions/{}.txt'.format(experiment), 'r') as f: @@ -16,7 +18,7 @@ def get_html(experimental_parameters: Dict): a('') with a.html(): with a.head(): - a.link(href="styling.css", rel='stylesheet', type="text/css") + a.link(href=css_path, rel='stylesheet', type="text/css") a.title(_t="Analysis Report") with a.body(): From f6917cd21be36a64a5163d8c88978bf0efd9ae38 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 26 Sep 2022 12:47:53 +0100 Subject: [PATCH 28/35] Automated running, still fnames problem --- eegnb/cli/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index 7d0ad31c..233a50b2 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -92,7 +92,8 @@ def askforreportcheck(): print(f"\n\n\nExperiment complete! Recorded data is saved @ {outfname}") if generatereport: - create_analysis_report(experiment, eegdevice, outfname) + # Error of filenames being multiple etc, needs to be handled + create_analysis_report(experiment=experiment, device_name=eegdevice, fnames=outfname) @main.command() From 76340c97f700ca9a1e07036146bd62312961fb1e Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 27 Sep 2022 14:22:08 +0100 Subject: [PATCH 29/35] Hahahah embedded images in html --- eegnb/analysis/analysis_report.html | 39 +++++++++++++++++++++++++++++ eegnb/analysis/pipelines.py | 11 +++----- 2 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 eegnb/analysis/analysis_report.html diff --git a/eegnb/analysis/analysis_report.html b/eegnb/analysis/analysis_report.html new file mode 100644 index 00000000..55dda918 --- /dev/null +++ b/eegnb/analysis/analysis_report.html @@ -0,0 +1,39 @@ + + + + + Analysis Report + + + +
+

Analysis Report

+

+ Experiment Name: {}
+ Subject Id: {}
+ Session Id: {}
+ EEG Device: {}
+ Drop Percentage: {}

+ This is an analysis report for the experiment.
For more information about the experiment, please visit the documentation +

+
+
+

Raw Epoch

+

+ The raw epoch is shown below. The raw epoch is the data that is recorded from the EEG headset. The raw epoch is then processed to remove noise and artifacts. +

+ Raw Epoch +
+
+

Stimulus Response

+

+ The stimulus response is shown below. The stimulus response is the data that is recorded from the EEG headset after removing noise and artifacts. +

+ Stimulus Response +
+ + \ No newline at end of file diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 329af234..91c27024 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -119,16 +119,13 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', # Filtering the data under a certain frequency range raw.filter(1,30, method='iir') - - # Configuring the image save path - image_save_path = get_save_directory(experiment=experiment, eegdevice=device_name, subject=subject, session=session, example=example, label='images') # Visualising the power spectrum fig = raw.plot_psd(fmin=1, fmax=30, show=False) # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. plt.tight_layout() - plt.savefig("{}\\power_spectrum.png".format(image_save_path)) + plt.savefig("power_spectrum.png") plt.show(block=False) plt.pause(10) plt.close() @@ -146,7 +143,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) print(epochs) - experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example, "drop_percent": (1 - len(epochs.events)/len(events)) * 100, "image_save_path":image_save_path} + experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example, "drop_percent": (1 - len(epochs.events)/len(events)) * 100} return epochs, experimental_parameters @@ -182,7 +179,7 @@ def make_erp_plot(epochs, experimental_parameters:Dict, conditions=OrderedDict(H # Makes sure that the axis labels are not cut out plt.tight_layout() - plt.savefig("{}\\erp_plot.png".format(experimental_parameters["image_save_path"])) + plt.savefig("erp_plot.png") plt.show(block=False) plt.pause(10) plt.close() @@ -194,7 +191,7 @@ def create_pdf(experimental_parameters:Dict): """Creates analysis report using the power spectrum and ERP plots that are saved in the directory""" # Unpack the experimental parameters - eegdevice, experiment, subject, session, example, drop_percentage, image_save_path = experimental_parameters.values() + eegdevice, experiment, subject, session, example, drop_percentage = experimental_parameters.values() # Getting the directory where the report should be saved save_dir = get_save_directory(experiment=experiment, eegdevice=eegdevice, subject=subject, session=session, example=example, label="analysis") From 77a5238ad93da78c1a5681a39d85ea8a374b2ba3 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Tue, 27 Sep 2022 14:28:41 +0100 Subject: [PATCH 30/35] Improving code --- eegnb/analysis/analysis_report.py | 24 +++++++++++++++++++++--- eegnb/analysis/pipelines.py | 5 +---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 9eebd1e6..5e3c9f64 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -5,12 +5,25 @@ from typing import Dict import os import eegnb +import base64 + a = Airium() + +def get_img_string(image_save_path): + """ Returns image as string to embed into the html report """ + return base64.b64encode(open(image_save_path, "rb").read()).decode() + def get_html(experimental_parameters: Dict): css_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis", "styling.css") - eeg_device, experiment, subject, session, example, drop_percentage, image_save_path = experimental_parameters.values() + eeg_device, experiment, subject, session, example, drop_percentage = experimental_parameters.values() + + image_save_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis") + erp_image_path = image_save_path + "\\erp_plot.png" + pos_image_path = image_save_path + "\\power_spectrum.png" + + """ Possibility of unique experiment text - decision to be made """ #experiment_text = "" #with open('experiment_descriptions/{}.txt'.format(experiment), 'r') as f: # experiment_text = f.readlines() @@ -51,13 +64,18 @@ def get_html(experimental_parameters: Dict): a.h2(_t="Raw Epoch") with a.p(): a("The raw epoch is shown below. The raw epoch is the data that is recorded from the EEG headset. The raw epoch is then processed to remove noise and artifacts.") - a.img(src="{}\\power_spectrum.png".format(image_save_path), alt="Raw Epoch") + a.img(src="data:image/png;base64, {}".format(get_img_string(pos_image_path)), alt="Raw Epoch") # Stimulus Response with a.div(id="Stimulus Response"): a.h2(_t="Stimulus Response") with a.p(): a("The stimulus response is shown below. The stimulus response is the data that is recorded from the EEG headset after removing noise and artifacts.") - a.img(src="{}\\erp_plot.png".format(image_save_path), alt="Stimulus Response") + a.img(src="data:image/png;base64, {}".format(get_img_string(erp_image_path)), alt="Stimulus Response") + + # Delete the images + os.remove(erp_image_path) + os.remove(pos_image_path) + # Return the html return str(a) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 91c27024..a116f847 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -5,7 +5,6 @@ To do: 1. Beautify analysis pdf 2. Handle cli automated errors for report creation -3. Implement interface to erp plot function for cli Usage: @@ -173,10 +172,7 @@ def make_erp_plot(epochs, experimental_parameters:Dict, conditions=OrderedDict(H # Autoscaling the y axis to a tight fit to the ERP for i in [0,1,2,3]: ax[i].autoscale(tight=True) - # for a in ax.ravel (): a.autoscale(tight=True) - # Saving the figure so it can be accessed by the pdf creation. Automatically deleted when added to the pdf. - # Makes sure that the axis labels are not cut out plt.tight_layout() plt.savefig("erp_plot.png") @@ -237,6 +233,7 @@ def create_analysis_report_(experiment, eegdevice, subject=None, session=None, d def example_analysis_report(): """ Example of how to use the analysis report function """ + experiment = ["visual-N170", "visual-P300"] experiment_choice = experiment[int(input("Choose an experiment: {} 0 or 1".format(experiment)))] From 0fb3b5f415c7835fc26c88c2f2ceb7ff16bc0780 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Fri, 30 Sep 2022 16:44:18 +0100 Subject: [PATCH 31/35] Okay now --- eegnb/analysis/analysis_report.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 5e3c9f64..254a5c9b 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -16,12 +16,14 @@ def get_img_string(image_save_path): def get_html(experimental_parameters: Dict): - css_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis", "styling.css") + # add variable to store the link + analysis_save_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis") + css_path = os.path.join(analysis_save_path, "styling.css") eeg_device, experiment, subject, session, example, drop_percentage = experimental_parameters.values() - - image_save_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis") - erp_image_path = image_save_path + "\\erp_plot.png" - pos_image_path = image_save_path + "\\power_spectrum.png" + + erp_image_path = os.path.join(os.getcwd(), "erp_plot.png") + pos_image_path = os.path.join(os.getcwd(), "power_spectrum.png") + """ Possibility of unique experiment text - decision to be made """ #experiment_text = "" From b38a805c2a668adb1853b55660c36804bf77295d Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 3 Oct 2022 16:19:24 +0100 Subject: [PATCH 32/35] Look at that --- eegnb/analysis/experiment_descriptions/visual-N170.txt | 2 ++ eegnb/analysis/experiment_descriptions/visual-P300.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 eegnb/analysis/experiment_descriptions/visual-N170.txt create mode 100644 eegnb/analysis/experiment_descriptions/visual-P300.txt diff --git a/eegnb/analysis/experiment_descriptions/visual-N170.txt b/eegnb/analysis/experiment_descriptions/visual-N170.txt new file mode 100644 index 00000000..ec9c24a7 --- /dev/null +++ b/eegnb/analysis/experiment_descriptions/visual-N170.txt @@ -0,0 +1,2 @@ +The N170 is a large negative event-related potential (ERP) component that occurs after the detection of faces, but not objects, scrambled faces, or other body parts such as hands. +In the experiment we aim to detect the N170 using faces and houses as our stimuli. \ No newline at end of file diff --git a/eegnb/analysis/experiment_descriptions/visual-P300.txt b/eegnb/analysis/experiment_descriptions/visual-P300.txt new file mode 100644 index 00000000..d756becd --- /dev/null +++ b/eegnb/analysis/experiment_descriptions/visual-P300.txt @@ -0,0 +1,2 @@ +The P300 is a positive event-related potential (ERP) that occurs around 300ms after perceiving a novel or unexpected stimulus. It is most commonly elicited through ‘oddball’ experimental paradigms, where a certain subtype of stimulus is presented rarely amidst a background of another more common type of stimulus. +In the experiment, we aimed to elicit P300 response using a visual oddball stimulation. \ No newline at end of file From d32a6d16cef27b07bf98bec7a5f5fdc67564276f Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 3 Oct 2022 16:20:06 +0100 Subject: [PATCH 33/35] Almost there just the two figures now --- eegnb/analysis/pipelines.py | 11 ++++++----- eegnb/cli/__main__.py | 4 ++-- eegnb/cli/introprompt.py | 12 ++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index a116f847..2c5643c1 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -140,9 +140,10 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', verbose=verbose, picks=picks) print('sample drop %: ', (1 - len(epochs.events)/len(events)) * 100) + print(len(epochs.events), 'events found') print(epochs) - experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example, "drop_percent": (1 - len(epochs.events)/len(events)) * 100} + experimental_parameters = {"eeg_device": device_name, "experiment_name": experiment, "subject_id": subject, "session_nb": session, "example_flag": example, "drop_percent": (1 - len(epochs.events)/len(events)) * 100, "epochs_chosen": len(epochs.events)} return epochs, experimental_parameters @@ -157,7 +158,7 @@ def make_erp_plot(epochs, experimental_parameters:Dict, conditions=OrderedDict(H ---------- epochs : MNE Epochs object conditions : OrderedDict holding the conditions to plot - ci: confidence interval + ci: confidence interval n_boot: number of bootstrap samples title: title of the plot diff_waveform: tuple of two integers indicating the channels to compare @@ -187,7 +188,7 @@ def create_pdf(experimental_parameters:Dict): """Creates analysis report using the power spectrum and ERP plots that are saved in the directory""" # Unpack the experimental parameters - eegdevice, experiment, subject, session, example, drop_percentage = experimental_parameters.values() + eegdevice, experiment, subject, session, example, drop_percentage, epochs_chosen = experimental_parameters.values() # Getting the directory where the report should be saved save_dir = get_save_directory(experiment=experiment, eegdevice=eegdevice, subject=subject, session=session, example=example, label="analysis") @@ -204,7 +205,7 @@ def create_pdf(experimental_parameters:Dict): # Informing the user that the report has been saved print('Analysis report saved to {}\n'.format(filepath)) - print("Open as pdf by clicking the following link: {}{}".format("file:///", filepath)) + print("Open the report by clicking the following link: {}{}".format("file:///", filepath)) def get_save_directory(experiment, eegdevice, subject, session, example, label): """ Returns save directory as a String for the analysis report """ @@ -235,7 +236,7 @@ def example_analysis_report(): """ Example of how to use the analysis report function """ experiment = ["visual-N170", "visual-P300"] - experiment_choice = experiment[int(input("Choose an experiment: {} 0 or 1".format(experiment)))] + experiment_choice = experiment[int(input("Choose an experiment: {} 0 or 1\n".format(experiment)))] if experiment_choice == "visual-N170": epochs, experimental_parameters = load_eeg_data(experiment_choice, example=True) diff --git a/eegnb/cli/__main__.py b/eegnb/cli/__main__.py index 233a50b2..b924fc50 100644 --- a/eegnb/cli/__main__.py +++ b/eegnb/cli/__main__.py @@ -79,7 +79,7 @@ def askforsigqualcheck(): askforsigqualcheck() def askforreportcheck(): - generatereport = input("\n\nGenerate Report? (Y/n): ").lower() != "n" + generatereport = input("\n\nGenerate Report? (Y/n): \n").lower() != "n" if dosigqualcheck: askforsigqualcheck() @@ -118,7 +118,7 @@ def create_analysis_report( """ if prompt: - example = input("Do you want to load an example experiment? (y/n)") + example = input("Do you want to load an example experiment? (y/n)\n") print() if example == 'y': example_analysis_report() diff --git a/eegnb/cli/introprompt.py b/eegnb/cli/introprompt.py index 8620216a..647651bb 100644 --- a/eegnb/cli/introprompt.py +++ b/eegnb/cli/introprompt.py @@ -96,7 +96,7 @@ def exp_prompt(runorzip:str='run') -> str: ) ) - exp_idx = int(input("\nEnter Experiment Selection: ")) + exp_idx = int(input("\nEnter Experiment Selection: \n")) exp_selection = list(experiments.keys())[exp_idx] print(f"Selected experiment: {exp_selection} \n") @@ -185,7 +185,7 @@ def analysis_device_prompt(): print("Please enter the integer value corresponding to your EEG device: \n") print("\n".join(f"[{i:2}] {board}" for i, board in enumerate(boards.values()))) - board_idx = int(input("\nEnter Board Selection: ")) + board_idx = int(input("\nEnter Board Selection: \n")) # Board_codes are the actual names to be passed to the EEG class board_code = list(boards.keys())[board_idx] @@ -198,14 +198,14 @@ def analysis_intro_prompt(): print("Do you have a filepath to a .csv file you would like to analyze? \n") print("[1] Yes \n") print("[0] No \n") - file_idx = int(input("Enter selection: ")) + file_idx = int(input("Enter selection: \n")) if file_idx == 1: print("Please enter the filepath to the .csv file you would like to analyze. \n") - filepath = input("Enter filepath: ") + filepath = input("Enter filepath: \n") subject, session = None, None else: - subject = int(input("Enter subject ID#: ")) - session = int(input("Enter session #: ")) + subject = int(input("Enter subject ID#: \n")) + session = int(input("Enter session #: \n")) filepath = None eegdevice = analysis_device_prompt() From 67391c8932fa86cbfc0a7a7c63e19cc33eb8a829 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Mon, 3 Oct 2022 16:29:07 +0100 Subject: [PATCH 34/35] Now --- eegnb/analysis/analysis_report.py | 26 +++++++++++++++++++------- eegnb/analysis/pipelines.py | 5 +---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/eegnb/analysis/analysis_report.py b/eegnb/analysis/analysis_report.py index 254a5c9b..9df3b88d 100644 --- a/eegnb/analysis/analysis_report.py +++ b/eegnb/analysis/analysis_report.py @@ -9,7 +9,15 @@ a = Airium() +def get_experiment_information(experiment:str): + analysis_save_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis") + file_path = os.path.join(analysis_save_path, "experiment_descriptions") + + with open(os.path.join(file_path, experiment + ".txt"), 'r') as f: + experiment_text = f.readlines() + return experiment_text + def get_img_string(image_save_path): """ Returns image as string to embed into the html report """ return base64.b64encode(open(image_save_path, "rb").read()).decode() @@ -19,10 +27,12 @@ def get_html(experimental_parameters: Dict): # add variable to store the link analysis_save_path = os.path.join(os.path.dirname(eegnb.__file__), "analysis") css_path = os.path.join(analysis_save_path, "styling.css") - eeg_device, experiment, subject, session, example, drop_percentage = experimental_parameters.values() + eeg_device, experiment, subject, session, example, drop_percentage, epochs_chosen = experimental_parameters.values() erp_image_path = os.path.join(os.getcwd(), "erp_plot.png") pos_image_path = os.path.join(os.getcwd(), "power_spectrum.png") + + experiment_text = get_experiment_information(experiment) """ Possibility of unique experiment text - decision to be made """ @@ -57,22 +67,24 @@ def get_html(experimental_parameters: Dict): a("Session Id: {}
".format(session)) a("EEG Device: {}
".format(eeg_device)) - a("Drop Percentage: {} %

".format(round(drop_percentage,2))) - a('This is an analysis report for the experiment.
For more information about the experiment, please visit the documentation') - #a(experiment_text) - + a('This is an analysis report for the experiment.
For more information about the experiment, please visit the documentation

') + a("{}
".format(experiment_text[0])) + a("{}
".format(experiment_text[1])) + # Raw Epoch with a.div(id="Raw Epoch"): a.h2(_t="Raw Epoch") with a.p(): - a("The raw epoch is shown below. The raw epoch is the data that is recorded from the EEG headset. The raw epoch is then processed to remove noise and artifacts.") + a("The power spectrum of the raw epoch is displayed below. The raw epoch is then processed to remove noise and artifacts.") a.img(src="data:image/png;base64, {}".format(get_img_string(pos_image_path)), alt="Raw Epoch") # Stimulus Response with a.div(id="Stimulus Response"): a.h2(_t="Stimulus Response") with a.p(): - a("The stimulus response is shown below. The stimulus response is the data that is recorded from the EEG headset after removing noise and artifacts.") + a("The stimulus response is shown below. The stimulus response is the amplitude response at the specific timescales where the response to the stimulus can be detected.
") + a("Epochs chosen: {}
".format(epochs_chosen)) + a("Drop Percentage: {} %

".format(round(drop_percentage,2))) a.img(src="data:image/png;base64, {}".format(get_img_string(erp_image_path)), alt="Stimulus Response") # Delete the images diff --git a/eegnb/analysis/pipelines.py b/eegnb/analysis/pipelines.py index 2c5643c1..a4604531 100644 --- a/eegnb/analysis/pipelines.py +++ b/eegnb/analysis/pipelines.py @@ -112,10 +112,7 @@ def load_eeg_data(experiment, subject=1, session=1, device_name='muse2016_bfn', raw = load_data(1,1, experiment=experiment, site='eegnb_examples', device_name=device_name, data_dir = eegnb_data_path) - - # Visualising the power spectrum - raw.plot_psd(show=False) - + # Filtering the data under a certain frequency range raw.filter(1,30, method='iir') From a46746522c83478591236fc7c32e76edc76c9bf3 Mon Sep 17 00:00:00 2001 From: Parvfect Date: Thu, 6 Oct 2022 12:20:04 +0100 Subject: [PATCH 35/35] Added attrdict to do with cli error --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee59cb25..c85486eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ pytest-shutil pyo>=1.0.3; platform_system == "Linux" keyboard==0.13.5 airium>=0.1.0 - +attrdict>=2.0.1 # This might try to build from source on linux (since there are no wheels for Linux on PyPI) . # You can pass `--find-links=https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04/` your `pip install` to use the prebuilt wheels at the link.