Skip to content

Commit

Permalink
Added imports to init to allow usage of functions in other projects; …
Browse files Browse the repository at this point in the history
…Added examples of import and usage in other projects
  • Loading branch information
jdpigeon committed May 17, 2018
1 parent 263dc17 commit c2e361c
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 36 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Compatible with Python 2.7 and Python 3.x.

## Usage

*Everything can be run using muse-lsl.py or you may integrate into other packages.
*Everything can be run as a CLI using muse-lsl.py

To stream data with LSL:

Expand All @@ -44,6 +44,20 @@ You can choose between gatt, bgapi, and bluemuse backends.
* bgapi - used with BLED112 dongle.
* bluemuse - used on Windows 10, native Bluetooth stack, requires [BlueMuse](https://github.com/kowalej/BlueMuse/tree/master/Dist) installation.

### Integration into other packages
If you want to integrate Muse LSL into your own Python project, you can import and use its functions as you would any Python package. Examples are available in the `examples` folder.

ex:
```Python
from muselsl import muse_stream

muses = muse_stream.list_muses()
muse_stream.stream(muses[0]['address'])

# Note: Streaming is synchronous, so code here will not execute until after the stream has been closed
print('Stream has ended')
```

## Common issues

1. `pygatt.exceptions.BLEError: Unexpected error when scanning: Set scan parameters failed: Operation not permitted` (Linux)
Expand Down
7 changes: 7 additions & 0 deletions examples/recordStream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from muselsl import lsl_record

# Note: an existing Python process running a Muse LSL stream is required
lsl_record.record(60)

# Note: Recording is synchronous, so code here will not execute until the stream has been closed
print('Recording has ended')
7 changes: 7 additions & 0 deletions examples/startMuseStream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from muselsl import muse_stream

muses = muse_stream.list_muses()
muse_stream.stream(muses[0]['address'])

# Note: Streaming is synchronous, so code here will not execute until the stream has been closed
print('Stream has ended')
9 changes: 9 additions & 0 deletions muselsl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from . import muse
from . import constants
from . import lsl_record
from . import lsl_viewer
from . import lsl_viewer_v2
from . import muse_record
from . import muse_stream

__version__ = "0.0.1"
2 changes: 1 addition & 1 deletion muselsl/lsl_viewer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from .constants import VIEW_BUFFER, VIEW_SUBSAMPLE
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import lfilter, lfilter_zi, firwin
from time import sleep
from pylsl import StreamInlet, resolve_byprop
import seaborn as sns
from threading import Thread
from constants import VIEW_SUBSAMPLE, VIEW_BUFFER


def view(window, scale, refresh, figure):
Expand Down
17 changes: 11 additions & 6 deletions muselsl/lsl_viewer_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ def on_key_press(self, event):
scale_x, scale_y = self.program['u_scale']
scale_x_new, scale_y_new = (scale_x * math.exp(1.0 * dx),
scale_y * math.exp(0.0 * dx))
self.program['u_scale'] = (max(1, scale_x_new), max(1, scale_y_new))
self.program['u_scale'] = (
max(1, scale_x_new), max(1, scale_y_new))
self.update()

def on_mouse_wheel(self, event):
Expand All @@ -205,7 +206,7 @@ def on_timer(self, event):
"""Add some data at the end of each signal (real-time signals)."""

samples, timestamps = self.inlet.pull_chunk(timeout=0.0,
max_samples=100)
max_samples=100)
if timestamps:
samples = np.array(samples)[:, ::-1]

Expand All @@ -221,7 +222,8 @@ def on_timer(self, event):
elif not self.filt:
plot_data = (self.data - self.data.mean(axis=0)) / self.scale

sd = np.std(plot_data[-int(self.sfreq):], axis=0)[::-1] * self.scale
sd = np.std(plot_data[-int(self.sfreq):],
axis=0)[::-1] * self.scale
co = np.int32(np.tanh((sd - 30) / 15)*5 + 5)
for ii in range(self.n_chans):
self.quality[ii].text = '%.2f' % (sd[ii])
Expand All @@ -231,7 +233,8 @@ def on_timer(self, event):
self.names[ii].font_size = 12 + co[ii]
self.names[ii].color = self.quality_colors[co[ii]]

self.program['a_position'].set_data(plot_data.T.ravel().astype(np.float32))
self.program['a_position'].set_data(
plot_data.T.ravel().astype(np.float32))
self.update()

def on_resize(self, event):
Expand All @@ -241,11 +244,13 @@ def on_resize(self, event):

for ii, t in enumerate(self.names):
t.transforms.configure(canvas=self, viewport=vp)
t.pos = (self.size[0] * 0.025, ((ii + 0.5) / self.n_chans) * self.size[1])
t.pos = (self.size[0] * 0.025,
((ii + 0.5) / self.n_chans) * self.size[1])

for ii, t in enumerate(self.quality):
t.transforms.configure(canvas=self, viewport=vp)
t.pos = (self.size[0] * 0.975, ((ii + 0.5) / self.n_chans) * self.size[1])
t.pos = (self.size[0] * 0.975,
((ii + 0.5) / self.n_chans) * self.size[1])

def on_draw(self, event):
gloo.clear()
Expand Down
36 changes: 19 additions & 17 deletions muselsl/muse-lsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ def __init__(self):
getattr(self, args.command)()

def list(self):
parser = argparse.ArgumentParser(description='List available Muse devices.')
parser = argparse.ArgumentParser(
description='List available Muse devices.')
parser.add_argument("-b", "--backend",
dest="backend", type=str, default="auto",
help="pygatt backend to use. can be auto, gatt or bgapi.")
dest="backend", type=str, default="auto",
help="pygatt backend to use. can be auto, gatt or bgapi.")
parser.add_argument("-i", "--interface",
dest="interface", type=str, default=None,
help="the interface to use, 'hci0' for gatt or a com port for bgapi.")
dest="interface", type=str, default=None,
help="the interface to use, 'hci0' for gatt or a com port for bgapi.")
args = parser.parse_args(sys.argv[2:])
import muse_stream
muses = muse_stream.list_muses(args.backend, args.interface)
Expand All @@ -68,7 +69,7 @@ def list(self):
(muse['name'], muse['address']))
else:
print('No Muses found')

def stream(self):
parser = argparse.ArgumentParser(
description='Start an LSL stream from Muse headset.')
Expand All @@ -93,23 +94,24 @@ def record(self):
parser = argparse.ArgumentParser(
description='Start LSL stream and record data from Muse headset.')
parser.add_argument("-a", "--address",
dest="address", type=str, default=None,
help="device MAC address.")
dest="address", type=str, default=None,
help="device MAC address.")
parser.add_argument("-n", "--name",
dest="name", type=str, default=None,
help="name of the device.")
dest="name", type=str, default=None,
help="name of the device.")
parser.add_argument("-b", "--backend",
dest="backend", type=str, default="auto",
help="pygatt backend to use. can be auto, gatt or bgapi")
dest="backend", type=str, default="auto",
help="pygatt backend to use. can be auto, gatt or bgapi")
parser.add_argument("-i", "--interface",
dest="interface", type=str, default=None,
help="the interface to use, 'hci0' for gatt or a com port for bgapi")
dest="interface", type=str, default=None,
help="the interface to use, 'hci0' for gatt or a com port for bgapi")
parser.add_argument("-f", "--filename",
dest="filename", type=str, default=None,
help="name of the recording file.")
dest="filename", type=str, default=None,
help="name of the recording file.")
args = parser.parse_args(sys.argv[2:])
import muse_record
muse_record.record(args.address, args.backend, args.interface, args.name, args.filename)
muse_record.record(args.address, args.backend,
args.interface, args.name, args.filename)

def viewlsl(self):
parser = argparse.ArgumentParser(
Expand Down
13 changes: 9 additions & 4 deletions muselsl/muse.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def connect(self, interface=None, backend='auto'):
self.interface = self.interface or 'hci0'
self.adapter = pygatt.GATTToolBackend(self.interface)
else:
self.adapter = pygatt.BGAPIBackend(serial_port=self.interface)
self.adapter = pygatt.BGAPIBackend(
serial_port=self.interface)

self.adapter.start()
self.device = self.adapter.connect(self.address)
Expand Down Expand Up @@ -140,9 +141,11 @@ def start(self):
if self.backend == 'bluemuse':
address = self.address if self.address is not None else self.name
if address is None:
subprocess.call('start bluemuse://start?streamfirst=true', shell=True)
subprocess.call(
'start bluemuse://start?streamfirst=true', shell=True)
else:
subprocess.call('start bluemuse://start?addresses={0}'.format(address), shell=True)
subprocess.call(
'start bluemuse://start?addresses={0}'.format(address), shell=True)
return

self._init_timestamp_correction()
Expand All @@ -158,7 +161,9 @@ def stop(self):
address = self.address if self.address is not None else self.name
if address is None:
subprocess.call('start bluemuse://stopall', shell=True)
else: subprocess.call('start bluemuse://stop?addresses={0}'.format(address), shell=True)
else:
subprocess.call(
'start bluemuse://stop?addresses={0}'.format(address), shell=True)
return

self._write_cmd([0x02, 0x68, 0x0a])
Expand Down
2 changes: 1 addition & 1 deletion muselsl/muse_record.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from muse import Muse
from .muse import Muse
from time import sleep, strftime, gmtime
import numpy as np
import pandas as pd
Expand Down
14 changes: 8 additions & 6 deletions muselsl/muse_stream.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from muse import Muse
from constants import NB_CHANNELS, SAMPLING_RATE, SCAN_TIMEOUT, LSL_CHUNK
from .muse import Muse
from .constants import NB_CHANNELS, SAMPLING_RATE, SCAN_TIMEOUT, LSL_CHUNK
from time import sleep
from pylsl import StreamInfo, StreamOutlet, local_clock
import pygatt
Expand All @@ -8,7 +8,7 @@


# Returns a list of available Muse devices
def list_muses(backend='auto', interface=''):
def list_muses(backend='auto', interface=None):
interface = None

if backend in ['auto', 'gatt', 'bgapi', 'bluemuse']:
Expand Down Expand Up @@ -55,7 +55,7 @@ def find_muse(name=None):
return muses[0]


def stream(address, backend, interface, name):
def stream(address, backend='auto', interface=None, name=None):
if backend != 'bluemuse':
if not address:
found_muse = find_muse(name)
Expand All @@ -65,13 +65,15 @@ def stream(address, backend, interface, name):
else:
address = found_muse['address']
name = found_muse['name']
print('Connecting to %s : %s...' % (name if name else 'Muse', address))
print('Connecting to %s : %s...' %
(name if name else 'Muse', address))

else:
if not address and not name:
print('Connecting to first device in BlueMuse list, see BlueMuse window...')
else:
print('Connecting to: ' + ':'.join(filter(None, [name, address])) + '...')
print('Connecting to: ' +
':'.join(filter(None, [name, address])) + '...')

info = info = StreamInfo('Muse', 'EEG', NB_CHANNELS, SAMPLING_RATE, 'float32',
'Muse%s' % address)
Expand Down

0 comments on commit c2e361c

Please sign in to comment.