Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add multiple installation options #263

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
423 changes: 423 additions & 0 deletions eegnb/analysis/analysis_utils.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion eegnb/analysis/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

# 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_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
Expand Down
204 changes: 204 additions & 0 deletions eegnb/analysis/streaming_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import copy
from copy import deepcopy
import math
import logging
import sys
from collections import OrderedDict
from glob import glob
from typing import Union, List
from time import sleep, time
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
from mne.channels import make_standard_montage
from mne.filter import create_filter
from matplotlib import pyplot as plt
from scipy import stats
from scipy.signal import lfilter, lfilter_zi

from eegnb import _get_recording_dir
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")


logger = logging.getLogger(__name__)



# Bjareholt Tools
# ==================
# From https://github.com/ErikBjare/thesis/blob/master/src/eegclassify/clean.py
# ------


def channel_filter(
X: np.ndarray,
n_chans: int,
sfreq: int,
device_backend: str,
device_name: str,
low: float = 3,
high: float = 40,
verbose: bool = False,
) -> np.ndarray:
"""Inspired by viewer_v2.py in muse-lsl"""
if device_backend == "muselsl":
pass
elif device_backend == "brainflow":
if 'muse' not in device_name: # hacky; muse brainflow devices do in fact seem to be in correct units
X = X / 1000 # adjust scale of readings
else:
raise ValueError(f"Unknown backend {device_backend}")

window = 10
n_samples = int(sfreq * window)
data_f = np.zeros((n_samples, n_chans))

af = [1.0]
bf = create_filter(data_f.T, sfreq, low, high, method="fir", verbose=verbose)

zi = lfilter_zi(bf, af)
filt_state = np.tile(zi, (n_chans, 1)).transpose()
filt_samples, filt_state = lfilter(bf, af, X, axis=0, zi=filt_state)

return filt_samples





def check(eeg: EEG, n_samples=256) -> pd.Series:
"""
Usage:
------

from eegnb.devices.eeg import EEG
from eegnb.analysis.utils import check
eeg = EEG(device='museS')
check(eeg, n_samples=256)

"""

df = eeg.get_recent(n_samples=n_samples)

# seems to be necessary to give brainflow cnxn time to settle
if len(df) != n_samples:
sleep(10)
df = eeg.get_recent(n_samples=n_samples)

assert len(df) == n_samples

n_channels = eeg.n_channels
sfreq = eeg.sfreq
device_backend = eeg.backend
device_name = eeg.device_name

vals = df.values[:, :n_channels]
df.values[:, :n_channels] = channel_filter(vals,
n_channels,
sfreq,
device_backend,
device_name)

std_series = df.std(axis=0)

return std_series



def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, thres_std_high=None, n_goods=2,n_inarow=5):
"""
Usage:
------
from eegnb.devices.eeg import EEG
from eegnb.analysis.utils import check_report
eeg = EEG(device='museS')
check_report(eeg)

The thres_std_low & thres_std_high values are the
lower and upper bound of accepted
standard deviation for a quality recording.

thresholds = {
bad: 15,
good: 10,
great: 1.5 // Below 1 usually indicates not connected to anything
}
"""

# 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]
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}")

CHECKMARK = "√"
CROSS = "x"

print(f"running check (up to) {n_times} times, with {pause_time}-second windows")
print(f"will stop after {n_goods} good check results in a row")

good_count=0

n_samples = int(pause_time*eeg.sfreq)

sleep(5)

for loop_index in range(n_times):
print(f'\n\n\n{loop_index+1}/{n_times}')
std_series = check(eeg, n_samples=n_samples)

indicators = "\n".join(
[
f" {k:>4}: {CHECKMARK if v >= thres_std_low and v <= thres_std_high else CROSS} (std: {round(v, 1):>5})"
for k, v in std_series.iteritems()
]
)
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)}")
good_count=0 # reset good checks count if there are any bad chans
else:
print('No bad channels')
good_count+=1

if good_count==n_goods:
print("\n\n\nAll good! You can proceed on to data collection :) ")
break

# after every n_inarow trials ask user if they want to cancel or continue
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_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


2 changes: 1 addition & 1 deletion examples/visual_cueing/01r__cueing_singlesub_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from mne.time_frequency import tfr_morlet

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data,plot_conditions
from eegnb.analysis.analysis_utils import load_data,plot_conditions
from eegnb.datasets import fetch_dataset

# sphinx_gallery_thumbnail_number = 1
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_cueing/02r__cueing_group_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

# EEG-Notebooks functions
from eegnb.datasets import datasets
from eegnb.analysis.utils import load_data
from eegnb.analysis.analysis_utils import load_data

# sphinx_gallery_thumbnail_number = 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from mne.time_frequency import tfr_morlet

# EEG-Noteooks functions
from eegnb.analysis.utils import load_data
from eegnb.analysis.analysis_utils import load_data
from eegnb.datasets import fetch_dataset

# sphinx_gallery_thumbnail_number = 1
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_n170/01r__n170_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from mne import Epochs,find_events

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data,plot_conditions
from eegnb.analysis.analysis_utils import load_data,plot_conditions
from eegnb.datasets import fetch_dataset

# sphinx_gallery_thumbnail_number = 3
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_n170/02r__n170_decoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from mne.decoding import Vectorizer

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data
from eegnb.analysis.analysis_utils import load_data
from eegnb.datasets import fetch_dataset

# Scikit-learn and Pyriemann ML functionalities
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_p300/01r__p300_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from mne import Epochs,find_events

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data,plot_conditions
from eegnb.analysis.analysis_utils import load_data,plot_conditions
from eegnb.datasets import fetch_dataset

# sphinx_gallery_thumbnail_number = 3
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_p300/02r__p300_decoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from mne.decoding import Vectorizer

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data
from eegnb.analysis.analysis_utils import load_data
from eegnb.datasets import fetch_dataset

# Scikit-learn and Pyriemann ML functionalities
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_ssvep/01r__ssvep_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from mne.time_frequency import tfr_morlet

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data,plot_conditions
from eegnb.analysis.analysis_utils import load_data,plot_conditions
from eegnb.datasets import fetch_dataset

# sphinx_gallery_thumbnail_number = 3
Expand Down
2 changes: 1 addition & 1 deletion examples/visual_ssvep/02r__ssvep_decoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from mne.decoding import Vectorizer

# EEG-Notebooks functions
from eegnb.analysis.utils import load_data
from eegnb.analysis.analysis_utils import load_data
from eegnb.datasets import fetch_dataset

# Scikit-learn and Pyriemann ML functionalities
Expand Down
72 changes: 53 additions & 19 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
# Main repo requirements
psychopy==2023.1.0
psychtoolbox

## ~~ Analysis Requirements ~~

scikit-learn>=0.23.2
pandas>=1.1.4
numpy>=1.19.4,<1.24 # due to outdated libs not changing the names after: https://github.com/numpy/numpy/pull/22607
mne>=0.20.8
seaborn>=0.11.0
pyriemann>=0.2.7
jupyter
gdown>=4.5.1
matplotlib>=3.3.3
pysocks>=1.7.1
pyserial>=3.5
h5py>=3.1.0
pytest-shutil
pyo>=1.0.3; platform_system == "Linux"
keyboard==0.13.5
airium>=0.1.0
attrdict>=2.0.1
attrdict3


## ~~ Streaming Requirements ~~

muselsl>=2.0.2
pylsl==1.10.5 # due to https://github.com/NeuroTechX/eeg-notebooks/issues/187
brainflow>=4.8.2
gdown>=4.5.1
matplotlib>=3.3.3
pysocks>=1.7.1
pyserial>=3.5
h5py>=3.1.0
pytest-shutil
pyo>=1.0.3; platform_system == "Linux"
keyboard==0.13.5
airium>=0.1.0
attrdict>=2.0.1
attrdict3
click


## ~~ Stimpres Requirements ~~

psychopy==2023.1.0
psychtoolbox
scikit-learn>=0.23.2
pandas>=1.1.4
numpy>=1.19.4,<1.24 # due to outdated libs not changing the names after: https://github.com/numpy/numpy/pull/22607
mne>=0.20.8
seaborn>=0.11.0
pysocks>=1.7.1
pyserial>=3.5
h5py>=3.1.0
Expand All @@ -26,7 +60,6 @@ attrdict3
# 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"
click

# pywinhook needs some special treatment since there are only wheels on PyPI for Python 3.7-3.8, and building requires special tools (swig, VS C++ tools)
# See issue: https://github.com/NeuroTechX/eeg-notebooks/issues/29
Expand All @@ -37,24 +70,25 @@ pywinhook @ https://github.com/ActivityWatch/wheels/raw/master/pywinhook/pyWinho
# See issue: https://github.com/psychopy/psychopy/issues/2876
pyglet==1.4.11 ; platform_system == "Windows"

# Test requirements
mypy
pytest
pytest-cov
nbval

# Types
types-requests

# Docs requirements
## ~~ Docsbuild Requirements ~~

oreHGA marked this conversation as resolved.
Show resolved Hide resolved
# Docs
sphinx
sphinx-gallery
sphinx_rtd_theme
sphinx-tabs
sphinx-copybutton
sphinxcontrib-httpdomain
numpydoc
recommonmark
versioneer
rst2pdf
docutils

# Tests
mypy
pytest
pytest-cov
nbval

# Types
types-requests