lsl example for the ganglion

Esse commit está contido em:
gabrielibagon
2017-02-16 23:40:53 -08:00
commit 65cb09e43b
6 arquivos alterados com 355 adições e 49 exclusões
+15 -49
Ver Arquivo
@@ -65,12 +65,12 @@ Please ensure [Python 2.7 is installed](https://www.python.org/downloads/) for a
### macOS
* install [Xcode](https://itunes.apple.com/ca/app/xcode/id497799835?mt=12)
### Linux
* Kernel version 3.6 or above
* ```libbluetooth-dev```
### Windows 8+
* [node-gyp requirements for Windows](https://github.com/TooTallNate/node-gyp#installation)
@@ -82,7 +82,7 @@ Please ensure [Python 2.7 is installed](https://www.python.org/downloads/) for a
See [@don](https://github.com/don)'s set up guide on [Bluetooth LE with Node.js and Noble on Windows](https://www.youtube.com/watch?v=mL9B8wuEdms).
## <a name="install"></a> Installation:
Install from npm:
```
@@ -91,7 +91,7 @@ npm install openbci-ganglion
## <a name="about"></a> About:
The Ganglion driver used by OpenBCI's Processing GUI and Electron Hub.
The Ganglion driver used by OpenBCI's Processing GUI and Electron Hub.
Check out the [**_automatic_** tests](https://codecov.io/gh/OpenBCI/OpenBCI_NodeJS_Ganglion) written for it!
@@ -333,13 +333,13 @@ Checks if the driver is connected to a board.
Checks if bluetooth is powered on. Cannot start scanning till this is true.
**_Returns_** {Boolean} - true if bluetooth is powered on.
**_Returns_** {Boolean} - true if bluetooth is powered on.
#### <a name="method-is-searching"></a> .isSearching()
Checks if noble is currently scanning. See [`.searchStart()`](#method-search-start) and [`.searchStop`()`](#method-search-stop`)
**_Returns_** {Boolean} - true if searching.
**_Returns_** {Boolean} - true if searching.
#### <a name="method-is-streaming"></a> .isStreaming()
@@ -373,7 +373,7 @@ Call to make `noble` start scanning for Ganglions.
**_maxSearchTime_** {Number}
The amount of time to spend searching. (Default is 20 seconds)
The amount of time to spend searching. (Default is 20 seconds)
**_Returns_** {Promise} - fulfilled if scan was started.
@@ -459,13 +459,13 @@ Returns an object with properties:
**_accelData_** {Array}
Array of floats for each dimension in g's.
Array of floats for each dimension in g's.
**NOTE:** Only present if `sendCounts` is `true`.
**_accelDataCounts_** {Array}
Array of integers for each dimension in counts.
Array of integers for each dimension in counts.
**NOTE:** Only present if `sendCounts` is `false`.
@@ -493,7 +493,7 @@ Emitted when there is an on the serial port.
#### <a name="event-impedance"></a> .on('impedance', callback)
Emitted when there is a new impedance available.
Emitted when there is a new impedance available.
Returns an object with properties:
@@ -529,13 +529,13 @@ Returns an object with properties:
**_channelData_** {Array}
Array of floats for each channel in volts..
Array of floats for each channel in volts..
**NOTE:** Only present if `sendCounts` is `true`.
**_channelDataCounts_** {Array}
Array of integers for each channel in counts.
Array of integers for each channel in counts.
**NOTE:** Only present if `sendCounts` is `false`.
@@ -577,46 +577,12 @@ Emitted when a noble scan is stopped.
### <a name="interfacing-with-other-tools-labstreaminglayer"></a> LabStreamingLayer
[LabStreamingLayer](https://github.com/sccn/labstreaminglayer) by SCCN is a stream management tool designed to time-synchronize multiple data streams, potentially from different sources, over a LAN network with millisecond accuracy (given configuration).
[LabStreamingLayer](https://github.com/sccn/labstreaminglayer) is a tool for streaming or recording time-series data. It can be used to interface with [Matlab](https://github.com/sccn/labstreaminglayer/tree/master/LSL/liblsl-Matlab), [Python](https://github.com/sccn/labstreaminglayer/tree/master/LSL/liblsl-Python), [Unity](https://github.com/xfleckx/LSL4Unity), and many other programs.
For example, a VR display device running a Unity simulation may, using the [LSL4Unity](https://github.com/xfleckx/LSL4Unity) library, emit string markers into LSL corresponding to events of interest (For the P300 ERP, this event would be the onset of an attended, unusual noise in a pattern of commonplace ones). The computer doing data collection via the OpenBCI_NodeJS library (potentially with 4ms accuracy) would then output into an LSL stream the EEG and AUX data. LSL can then synchronize the two clocks relative to each other before inputting into a different program or toolkit, like [BCILAB](https://github.com/sccn/BCILAB) for analysis to trigger responses in the Unity display.
To use LSL with the NodeJS SDK, go to our [labstreaminglayer example](https://github.com/OpenBCI/OpenBCI_NodeJS_Ganglion/tree/master/examples/labstreaminglayer), which contains code that is ready to start an LSL stream of OpenBCI data.
This requires OpenBCI_NodeJS exporting data into LSL. Currently, there does not exist a pre-built NodeJS module for LSL, though LSL comes with tools that could possibly allow creation of one. In the meantime, the simpler route is to use a concurrent python script (driven by NodeJS module [python-shell](https://www.npmjs.com/package/python-shell)) to handoff the data to LSL for you, like so:
Follow the directions in the [readme](https://github.com/OpenBCI/OpenBCI_NodeJS_Ganglion/blob/master/examples/labstreaminglayer/readme.md) to get started.
In your NodeJS code, before initializing/connecting to the OpenBCIBoard:
```js
// Construct LSL Handoff Python Shell
var PythonShell = require('python-shell');
var lsloutlet = new PythonShell('LslHandoff.py');
lsloutlet.on('message', function(message){
console.log('LslOutlet: ' + message);
});
console.log('Python Shell Created for LSLHandoff');
```
In your NodeJS code, when reading samples:
```js
st = sample.channelData.join(' ')
//getTime returns milliseconds since midnight 1970/01/01
var s = ''+ sample.timeStamp + ': '+ st
lsloutlet.send(s)
```
in LSLHandoff.py:
```py
from pylsl import StreamInfo, StreamOutlet
info = StreamInfo('OpenBCI_EEG', 'EEG', 4, 200, 'float32', '[RANDOM NUMBER HERE]')
outlet = StreamOutlet(info)
while True:
strSample = raw_input().split(': ',1)
sample = map(float, strSample[1].split(' '))
stamp = float(strSample[0])
outlet.push_sample(sample, stamp)
print('Pushed Sample At: ' + strSample[0])
```
AUX data would be done the same way in a separate LSL stream.
## <a name="developing"></a> Developing:
### <a name="developing-running"></a> Running:
+80
Ver Arquivo
@@ -0,0 +1,80 @@
import json
import sys
import numpy as np
import time
import zmq
from pylsl import StreamInfo, StreamOutlet
class Interface:
def __init__(self, verbose=False):
context = zmq.Context()
self._socket = context.socket(zmq.PAIR)
self._socket.connect("tcp://localhost:3004")
self.verbose = verbose
if self.verbose:
print("Client Ready!")
# Send a quick message to tell node process we are up and running
self.send(json.dumps({
'action': 'started',
'command': 'status',
'message': time.time()*1000.0
}))
def send(self, msg):
"""
Sends a message to TCP server
:param msg: str
A string to send to node TCP server, could be a JSON dumps...
:return: None
"""
if self.verbose:
print('<- out ' + msg)
self._socket.send_string(msg)
return
def recv(self):
"""
Checks the ZeroMQ for data
:return: str
String of data
"""
return self._socket.recv()
def initializeOutlet(interface):
"""
Initializes and returns an LSL outlet
:param interface: Interface
the Python interface to communicate with node
:return: StreamOutlet
returns a labstreaminglayer StreamOutlet
"""
info = StreamInfo('OpenBCI_EEG', 'EEG', 4, 256, 'float32', 'openbci12345')
outlet = StreamOutlet(info)
return outlet
def main(argv):
verbose = False
# Create a new python interface.
interface = Interface(verbose)
# Create a new labstreaminglayer outlet
outlet = initializeOutlet(interface);
# Stream incoming data to LSL
while True:
msg = interface.recv()
try:
dicty = json.loads(msg)
message = dicty.get('message')
data = message.get('channelDataCounts')
timeStamp = message.get('timeStamp')
outlet.push_sample(data,timeStamp)
except BaseException as e:
print(e)
if __name__ == '__main__':
main(sys.argv[1:])
+109
Ver Arquivo
@@ -0,0 +1,109 @@
const Ganglion = require('openbci-ganglion').Ganglion;
var portPub = 'tcp://127.0.0.1:3004';
var zmq = require('zmq-prebuilt');
var socket = zmq.socket('pair');
var verbose = false;
let ganglion = new Ganglion({
nobleAutoStart: true,
sendCounts: true,
verbose: true
}, (error) => {
if (error) {
console.log(error);
} else {
if (verbose) {
console.log('Ganglion initialize completed');
}
}
});
ganglion.once('ganglionFound', (peripheral) => {
ganglion.searchStop();
ganglion.on('sample', (sample) => {
sendToPython({
action: 'process',
command: 'sample',
message: sample
});
});
ganglion.once('ready', () => {
ganglion.streamStart();
});
ganglion.connect(peripheral);
});
// ganglion.searchStart();
function exitHandler (options, err) {
if (options.cleanup) {
if (verbose) console.log('clean');
ganglion.manualDisconnect = true;
ganglion.disconnect();
ganglion.removeAllListeners('droppedPacket');
ganglion.removeAllListeners('accelerometer');
ganglion.removeAllListeners('sample');
ganglion.removeAllListeners('message');
ganglion.removeAllListeners('impedance');
ganglion.removeAllListeners('close');
ganglion.removeAllListeners('error');
ganglion.removeAllListeners('ganglionFound');
ganglion.removeAllListeners('ready');
ganglion.destroyNoble();
}
if (err) console.log(err.stack);
if (options.exit) {
if (verbose) console.log('exit');
if (ganglion.isSearching()) {
ganglion.searchStop().catch(console.log);
}
ganglion.manualDisconnect = true;
ganglion.disconnect(true).catch(console.log);
process.exit(0);
}
}
// ZMQ
socket.bind(portPub, function (err) {
if (err) throw err;
console.log(`bound to ${portPub}`);
});
/**
* Used to send a message to the Python process.
* @param {Object} interProcessObject The standard inter-process object.
* @return {None}
*/
var sendToPython = (interProcessObject, verbose) => {
if (verbose) {
console.log(`<- out ${JSON.stringify(interProcessObject)}`);
}
if (socket) {
socket.send(JSON.stringify(interProcessObject));
}
};
if (process.platform === 'win32') {
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('SIGINT', function () {
process.emit('SIGINT');
});
}
// do something when app is closing
process.on('exit', exitHandler.bind(null, {
cleanup: true
}));
// catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, {
exit: true
}));
// catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, {
exit: true
}));
+32
Ver Arquivo
@@ -0,0 +1,32 @@
{
"name": "labstreaminglayer",
"version": "1.0.0",
"description": "labstreaminglayer example",
"main": "index.js",
"scripts": {
"start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"",
"start-node": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"python",
"openbci",
"node",
"labstreaminglayer",
"LSL"
],
"author": "AJ Keller",
"license": "MIT",
"dependencies": {
"openbci-ganglion": "^0.4.3",
"zmq-prebuilt": "^2.1.0",
"noble": "1.7.0"
},
"devEngines": {
"node": "<=6.x",
"npm": ">=3.x"
},
"devDependencies": {
"concurrently": "^3.1.0"
}
}
+107
Ver Arquivo
@@ -0,0 +1,107 @@
# OpenBCI Ganglion Node SDK to Lab Streaming Layer
## About
This code provides an example of how to stream OpenBCI Ganglion data through the [lab streaming layer](https://github.com/sccn/labstreaminglayer) using the NodeJS SDK.
Follow the steps in this README to start streaming. The code is ready to run as-is, but can be modified and extended to customize how you are sending your data. This is designed to be used with the **OpenBCI Ganglion** (for **Ganglion support**, see the [Ganglion Node SDK](https://github.com/OpenBCI/OpenBCI_NodeJS/tree/master/examples/labstreaminglayer)).
## Prerequisites
* [Python](https://www.python.org/downloads/)
* [ZeroMQ](http://zeromq.org/bindings:python)
```py
pip install pyzmq
```
* [Node.js LTS](https://nodejs.org/en/)
* [Lab Streaming Layer](https://github.com/sccn/labstreaminglayer)
```py
pip install pylsl
```
## Installation
First, install Python dependencies:
```bash
python setup.py install
```
Next, install NodeJS dependencies:
```bash
npm install
```
Note: depending on your computer settings, you may need to run as administrator or with `sudo`.
## Running
```
npm start
```
For running just the node, for example if you were running the python in a separate ide and debugging, it's useful.
```python
npm run start-node
```
Note: depending on your computer settings, you may need to run as administrator or with `sudo`.
## Writing Lab Streaming Layer Code
If you would like to use lab streaming layer in a custom OpenBCI NodeJS application, you must include an instance of the OpenBCI NodeJS library and an instance of a Python interface. A basic example is shown below:
index.js
```js
const Ganglion = require('openbci-ganglion').Ganglion;
var portPub = 'tcp://127.0.0.1:3004';
var zmq = require('zmq-prebuilt');
var socket = zmq.socket('pair');
let ganglion = new Ganglion();
socket.bind(portPub)
ganglion.once('ganglionFound', (peripheral) => {
ganglion.searchStop();
ganglion.on('sample', (sample) => {
socket.send(JSON.stringify({message: sample}))
});
ganglion.once('ready', () => {
ganglion.streamStart();
});
ganglion.connect(peripheral);
});
// ZMQ
/* Insert additional exit handlers and cleanup below*/
```
handoff.py
```python
import json
import zmq
from pylsl import StreamInfo, StreamOutlet
# Create ZMQ socket
context = zmq.Context()
_socket = context.socket(zmq.PAIR)
_socket.connect("tcp://localhost:3004")
# Create a new labstreaminglayer outlet
numChans = 4;
sampleRate = 256;
info = StreamInfo('OpenBCI_EEG', 'EEG', numChans, sampleRate, 'float32', 'openbci_12345')
outlet = StreamOutlet(info)
# Stream incoming data to LSL
while True:
msg = _socket.recv()
try:
dicty = json.loads(msg)
message = dicty.get('message')
data = message.get('channelDataCounts')
timeStamp = message.get('timeStamp')
outlet.push_sample(data,timeStamp)
except BaseException as e:
print(e)
```
## Contributing
Please PR if you have code to contribute!
+12
Ver Arquivo
@@ -0,0 +1,12 @@
from setuptools import setup, find_packages
setup(name='openbci-node-labstreaminglayer',
version='0.0.1',
description='Labstreaminglayer with NodeJS',
url='',
author='AJ Keller',
author_email='pushtheworldllc@gmail.com',
license='MIT',
packages=find_packages(),
install_requires=['numpy', 'pyzmq','pylsl'],
zip_safe=False)