Skip to content

Commit

Permalink
MacOS Silicon support + minor doc updates (#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
oreHGA authored Sep 5, 2024
1 parent e03c52d commit ab1a444
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 46 deletions.
Binary file added .DS_Store
Binary file not shown.
8 changes: 1 addition & 7 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,4 @@ jobs:
if: github.ref == 'refs/heads/master' # TODO: Deploy seperate develop-version of docs?
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: doc/_build/html
- name: Deploy Dev Docs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: doc/_build/html
destination_dir: develop
publish_dir: doc/_build/html
19 changes: 17 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,26 @@ jobs:
if: "startsWith(runner.os, 'Linux')"
run: |
make install-deps-wxpython
- name: Install conda
uses: conda-incubator/setup-miniconda@v3
with:
environment-file: environment.yml
auto-activate-base: true
python-version: 3.8
activate-environment: EEG-ExPy
channels: conda-forge
miniconda-version: "latest"
- name: Install dependencies via conda
shell: bash -el {0}
run: |
conda info
conda activate EEG-ExPy
- name: Install dependencies
shell: bash -el {0}
run: |
make build
- name: Run eegnb install test
shell: bash
shell: bash -el {0}
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
Xvfb :0 -screen 0 1024x768x24 -ac +extension GLX +render -noreset &> xvfb.log &
Expand All @@ -65,7 +80,7 @@ jobs:
eegnb --help
eegnb runexp --help
- name: Run examples with coverage
shell: bash
shell: bash -el {0}
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
Xvfb :0 -screen 0 1024x768x24 -ac +extension GLX +render -noreset &> xvfb.log &
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ coverage.xml
htmlcov

# PyCharm
.idea/
.idea/

*.DS_Store*
55 changes: 35 additions & 20 deletions doc/getting_started/running_experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,20 @@ The first step is to import all of the necessary library dependencies. These are
```python
from eegnb import generate_save_fn
from eegnb.devices.eeg import EEG
from eegnb.experiments.visual_n170 import n170
from eegnb.experiments import VisualN170
```

Next we need to define session parameters which are otherwise handled via input prompts in the run `run_notebooks.py` script. After we define the session parameters we will pass them to the file name generator.

```python
board_name = 'cyton'
experiment = 'visual_n170'
session = 1
subject = 1
record_duration = 120
board_name = "muse2" # board name
experiment_name = "visual_n170" # experiment name
subject_id = 0 # test subject id
session_nb = 0 # session number
record_duration = 120 # recording duration

# Create output filename
save_fn = generate_save_fn(board_name, experiment, subject, session)
save_fn = generate_save_fn(board_name, experiment_name, subject_id, session_nb)
```

Next it is necessary to call the `eegnb.devices.eeg.EEG` class which handles all of the backend processes related to each device.
Expand All @@ -113,31 +113,46 @@ eeg_device = EEG(device=board_name)
Finally, we call the `present` method of the class corresponding to our desired experiment, in this case the visual N170. We pass both the EEG device and generated save file name in order to collect and save data. The presentation can also be run without an EEG device/save file for testing and debugging.

```python
n170.present(duration=record_duration, eeg=eeg_device, save_fn=save_fn)
experiment = VisualN170(duration=record_duration, eeg=eeg_device, save_fn=save_fn, use_vr=False)

experiment.run()
```

All together the example script looks like
```python
###################################################################################################
# Setup
# ---------------------
#
# Imports
from eegnb import generate_save_fn
from eegnb.devices.eeg import EEG
from eegnb.experiments.visual_n170 import n170
from eegnb.experiments import VisualN170

# Define some variables
board_name = 'cyton'
experiment = 'visual_n170'
session = 1
subject = 1
record_duration = 120
board_name = "muse2" # board name
experiment_name = "visual_n170" # experiment name
subject_id = 0 # test subject id
session_nb = 0 # session number
record_duration = 120 # recording duration

# Create output filename
save_fn = generate_save_fn(board_name, experiment, subject, session)
# generate save path
save_fn = generate_save_fn(board_name, experiment_name, subject_id, session_nb)

# Setup EEG device
# create device object
eeg_device = EEG(device=board_name)

# Run stimulus presentation
n170.present(duration=record_duration, eeg=eeg_device, save_fn=save_fn)
# Experiment type
experiment = VisualN170(duration=record_duration, eeg=eeg_device, save_fn=save_fn, use_vr=False)

###################################################################################################
# Run experiment
# ---------------------
#
experiment.run()

# Saved csv location
print("Recording saved in", experiment.save_fn)
```


Expand All @@ -162,7 +177,7 @@ The N170 experiment for example, can have its stimulus displayed on the VR heads

```python
# Run stimulus presentation with VR enabled.
n170.present(duration=record_duration, eeg=eeg_device, save_fn=save_fn, use_vr=True)
experiment = VisualN170(duration=record_duration, eeg=eeg_device, save_fn=save_fn, use_vr=True)
```

###
Expand Down
6 changes: 6 additions & 0 deletions doc/getting_started/streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ be run to begin the notebooks interfacing with the bluemuse backend.
**Backend:** Brainflow
**Needed Parameters:** No additional parameters are needed to connect to the Notion. It is necessary however to make sure the Notion is on the same network and readable by Neurosity's developer console.

### Neurosity Crown
![fig](../img/notion.png)
**Device Name:** *'crown'*
**Backend:** Brainflow
**Needed Parameters:** No additional parameters are needed to connect to the Notion. It is necessary however to make sure the Notion is on the same network and readable by Neurosity's developer console.

#### Connecting on Windows
In order to connect to the Notion on Windows you must first turn off your network firewall for the Open Sound Control (OSC) protocol to function for the notion.

Expand Down
1 change: 0 additions & 1 deletion eegnb/analysis/analysis_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from glob import glob
from typing import Union, List
from time import sleep, time
import keyboard
import os

import pandas as pd
Expand Down
18 changes: 13 additions & 5 deletions eegnb/analysis/streaming_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from glob import glob
from typing import Union, List
from time import sleep, time
import keyboard
from pynput import keyboard
import os

import pandas as pd
Expand Down Expand Up @@ -192,12 +192,20 @@ def check_report(eeg: EEG, n_times: int=60, pause_time=5, thres_std_low=None, th
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(f"Starting next cycle in 5 seconds, press C and enter to cancel")
c_key_pressed = False

def update_key_press(key):
if key.char == 'c':
globals().update(c_key_pressed=True)
listener = keyboard.Listener(on_press=update_key_press)
listener.start()
while time() < prompt_time + 5:
if c_key_pressed:
print("\nStopping signal quality checks!")
flag = True
break
break
listener.stop()
if flag:
break

Expand Down
1 change: 0 additions & 1 deletion eegnb/analysis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from glob import glob
from typing import Union, List
from time import sleep, time
import keyboard
import os

import pandas as pd
Expand Down
1 change: 0 additions & 1 deletion eegnb/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ def create_analysis_report(
experiment, eegdevice, subject, session, site, filepath = analysis_intro_prompt()
analysis_report(experiment, eegdevice, subject, session, site, filepath)


@main.command()
@click.option("-ed", "--eegdevice", help="EEG device to use", required=True)
def checksigqual(eegdevice: str):
Expand Down
3 changes: 1 addition & 2 deletions eegnb/cli/introprompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def analysis_device_prompt():
def analysis_intro_prompt():

# check if user has filepath
print("Welcome to NeurotechX EEG Notebooks\n")
print("Welcome to NeurotechX EEG-ExPy\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")
Expand All @@ -220,7 +220,6 @@ def analysis_intro_prompt():
return experiment, eegdevice, subject, session, site, filepath



def intro_prompt_zip() -> Tuple[str,str]:
"""This function handles the user prompts for inputting information for zipping their function."""

Expand Down
4 changes: 3 additions & 1 deletion eegnb/experiments/auditory_oddball/aob.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import numpy as np
from pandas import DataFrame
from psychopy import prefs
# PTB does not yet support macOS Apple Silicon, need to fall back to sounddevice.
prefs.hardware['audioLib'] = ['sounddevice']
from psychopy import visual, core, event, sound

from time import time
Expand All @@ -8,7 +11,6 @@
from typing import Optional



class AuditoryOddball(Experiment.BaseExperiment):

def __init__(self, duration=120, eeg: Optional[EEG]=None, save_fn=None, n_trials = 2010, iti = 0.3, soa = 0.2, jitter = 0.2, secs=0.2, volume=0.8, random_state=42, s1_freq="C", s2_freq="D", s1_octave=5, s2_octave=6):
Expand Down
6 changes: 6 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
channels:
- conda-forge
dependencies:
- python=3.8
- pytables # install pytables for macOS arm64, so do not need to build from source.
- liblsl # install liblsl to prevent error on macOS and Ubuntu: "RuntimeError: LSL binary library file was not found."
23 changes: 18 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ pyserial>=3.5
h5py>=3.1.0
pytest-shutil
pyo>=1.0.3; platform_system == "Linux"
keyboard==0.13.5
#pynput requires pyobjc, psychopy requires a version less than 8, setting pyobjc to
# a specific version prevents an endless dependency resolution loop.
pyobjc==7.3; sys_platform == 'darwin'
airium>=0.1.0
attrdict>=2.0.1
attrdict3
Expand All @@ -24,14 +26,20 @@ attrdict3
## ~~ Streaming Requirements ~~

muselsl>=2.0.2
pylsl==1.10.5 # due to https://github.com/NeuroTechX/eeg-notebooks/issues/187
# Upgrade from 1.10.5 to 1.16.2 so the arm64 lib is available to macOS Apple Silicon for preventing error:
# pylsl/liblsl64.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e' or 'arm64'))
pylsl==1.16.2
brainflow>=4.8.2
pysocks>=1.7.1
pyserial>=3.5
h5py>=3.1.0
pytest-shutil
pyo>=1.0.3; platform_system == "Linux"
keyboard==0.13.5
#pynput requires pyobjc, psychopy requires a version less than 8, setting pyobjc to
# a specific version prevents an endless dependency resolution loop.
pyobjc==7.3; sys_platform == 'darwin'
#Removed keyboard dependency due segmentation fault on Apple Silicon: https://github.com/boppreh/keyboard/issues/507
pynput
airium>=0.1.0
attrdict>=2.0.1
attrdict3
Expand All @@ -40,7 +48,13 @@ click

## ~~ Stimpres Requirements ~~

psychopy==2023.1.0
#pynput requires pyobjc, psychopy requires a version less than 8, setting pyobjc to
# a specific version prevents an endless dependency resolution loop.
pyobjc==7.3; sys_platform == 'darwin'
#upgrade psychopy to use newer wxpython dependency which is prebuilt for m1 support.
psychopy==2023.2.2
# PTB does not yet support macOS Apple Silicon, need to fallback to sounddevice.
psychopy-sounddevice
psychtoolbox
scikit-learn>=0.23.2
pandas>=1.1.4
Expand All @@ -52,7 +66,6 @@ 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
Expand Down

0 comments on commit ab1a444

Please sign in to comment.