Skip to content
This repository has been archived by the owner on Jul 29, 2020. It is now read-only.

Commit

Permalink
refactor back-end
Browse files Browse the repository at this point in the history
Closes #23
  • Loading branch information
alexcastillo committed Jun 1, 2016
1 parent cc3eaf8 commit 7661dad
Show file tree
Hide file tree
Showing 13 changed files with 456 additions and 286 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"postinstall": "typings install",
"lint": "tslint \"src/**/*.ts\"",
"format": "clang-format -i -style=file --glob=src/**/*.ts",
"visualize": "concurrently \"ng serve\" \"node visualizer\" ",
"simulate": "concurrently \"ng serve\" \"node visualizer simulate\" ",
"visualize": "concurrently \"ng serve\" \"node src/server/app\" ",
"simulate": "concurrently \"ng serve\" \"node src/server/app simulate\" ",
"pree2e": "webdriver-manager update",
"e2e": "protractor"
},
Expand Down
80 changes: 80 additions & 0 deletions src/server/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

/**
* External Dependencies
*/
var argv = require('yargs').argv;
var OpenBCIBoard = require('openbci-sdk');
var io = require('socket.io')(process.env.app_port || 8080);

/**
* Internal Dependencies
*/
const Providers = require('./providers');
const Modules = require('./modules');
const Utils = require('./utils');

// OpenBCI
const connector = new OpenBCIBoard.OpenBCIBoard({
verbose: true
});

const Signals = new Providers.Signals({ connector, io });
const TimeSeries = new Modules.TimeSeries({ connector, io, signalEvent: Signals.signalEvent });
const Topo = new Modules.Topo({ connector, io, signalEvent: Signals.signalEvent });
const FFT = new Modules.FFT({ connector, io, signalEvent: Signals.signalEvent });

global.scale = 1.5;

connector.autoFindOpenBCIBoard()
.then(onBoardFind)
.catch(() => {
if (!!(argv._[0] && argv._[0] === 'simulate')) {
global.scale = 4;
connector
.connect(OpenBCIBoard.OpenBCIConstants.OBCISimulatorPortName)
.then(onBoardConnect);
}
});

// Board find handler
function onBoardFind (portName) {
if (portName) {
console.log('board found', portName);
connector.connect(portName)
.then(onBoardConnect);
}
}

// Board connect handler
function onBoardConnect () {
connector.on('ready', onBoardReady);
}

// Board ready handler
function onBoardReady () {
connector.streamStart();
TimeSeries.listen();
FFT.listen();
Topo.listen();
connector.on('sample', onSample);
}

function onSample (sample) {
Signals.buffer(sample);
}

/**
* disconnectBoard
*/
function disconnectBoard () {
connector.streamStop()
.then(function () {
connector.disconnect().then(function () {
console.log('board disconnected');
process.exit();
});
});
}

process.on('SIGINT', disconnectBoard);
80 changes: 80 additions & 0 deletions src/server/modules/fft.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

const dsp = require('dsp.js');
const Utils = require('../utils');

module.exports = class FFT {

constructor ({ connector, io, signalEvent }) {
this.connector = connector;
this.io = io;
this.signalEvent = signalEvent;
this.bins = 512; // aka ~2 seconds
this.bufferSize = 512;
this.sampleRate = this.connector.sampleRate(); // aka 250
this.spectrums = [[],[],[],[],[],[],[],[]];
this.spectrumsByBand = [];
this.labels = [];
this.bands = {
delta: [1, 3],
theta: [4, 8],
alpha: [8, 12],
beta: [13, 30],
gamma: [30, 100]
};
}

listen () {
this.signalEvent.on('bci:signal', (signals) => {
this.signalsToFFT(signals);
this.scaleLabels();
this.filterBands();
this.filterLabels();
this.emit();
});
}

signalsToFFT (signals) {
signals.forEach((signal, index) => {
//signal = Utils.filter.process(signal);
let fft = new dsp.FFT(this.bufferSize, this.sampleRate);
fft.forward(signal);
this.spectrums[index] = Utils.data.parseObjectAsArray(fft.spectrum);
this.spectrums[index] = Utils.signal.voltsToMicrovolts(this.spectrums[index], true);
});
}

scaleLabels () {
this.labels = new Array(this.bins / 2).fill()
.map((label, index) => {
return Math.ceil(index * (this.sampleRate / this.bins));
});
}

filterBands () {
for (let band in this.bands) {
this.spectrumsByBand[band] = Utils.filter.filterBand(this.spectrums, this.labels, this.bands[band]);
}
}

filterLabels () {
// Skip every 8, add uni (too many labels issue)
this.labels = this.labels.map((label, index, labels) => {
return (index % 8 === 0 || index === (labels.length - 1)) ? label + ' Hz' : '';
});
}

emit () {
this.io.emit('bci:fft', {
data: this.spectrums,
labels: this.labels,
theta: this.spectrumsByBand.theta.spectrums,
delta: this.spectrumsByBand.delta.spectrums,
alpha: this.spectrumsByBand.alpha.spectrums,
beta: this.spectrumsByBand.beta.spectrums,
gamma: this.spectrumsByBand.gamma.spectrums
});
}

}

11 changes: 11 additions & 0 deletions src/server/modules/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const FFT = require('./fft.module');
const TimeSeries = require('./time-series.module');
const Topo = require('./topo.module');

module.exports = {
FFT,
TimeSeries,
Topo
}
62 changes: 62 additions & 0 deletions src/server/modules/time-series.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

const Utils = require('../utils');

module.exports = class TimeSeries {

constructor ({ connector, io, signalEvent }) {
this.connector = connector;
this.io = io;
this.signalEvent = signalEvent;
this.sampleRate = this.connector.sampleRate();
this.timeSeriesWindow = 5; // in seconds
this.timeline = Utils.data.generateTimeline(20, 2, 's');
this.timeSeries = this.create(8); // 8 channels
this.amplitudes = [];
}

create (channelAmount) {
let timeSeries = new Array(channelAmount).fill([]);
return timeSeries.map((channel, channelNumber) => {
return new Array((this.sampleRate * this.timeSeriesWindow))
.fill(0)
.map((amplitude) => {
// @TODO: Migrate scale (4) elsewhere
return Utils.signal.offsetForGrid(amplitude, channelNumber, timeSeries.length, 4);
});
});
}

listen () {
this.signalEvent.on('bci:signal', (signal) => {
this.offsetForGrid(signal);
this.signalToAmplitudes(signal);
this.emit();
});
}

offsetForGrid (signal) {
this.timeSeries.forEach((channel, channelIndex) => {
// @TODO: Migrate scale (4) elsewhere
channel.push(Utils.signal.offsetForGrid(signal[channelIndex][signal[channelIndex].length - 1], channelIndex, this.timeSeries.length, 4));
channel.shift();
});
}

signalToAmplitudes (signal) {
this.amplitudes = signal.map((channel) => {
return (Math.round(Utils.signal.voltsToMicrovolts(channel[channel.length - 1])[0])) + ' uV';
});
}

emit () {
this.io.emit('bci:time', {
data: this.timeSeries,
amplitudes: this.amplitudes,
timeline: this.timeline
});
}

}


49 changes: 49 additions & 0 deletions src/server/modules/topo.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

const topogrid = require('topogrid');

const Utils = require('../utils');

module.exports = class Topo {

constructor ({ connector, io, signalEvent }) {
this.connector = connector;
this.io = io;
this.signalEvent = signalEvent;
this.sampleRate = this.connector.sampleRate();

/**
* params: Parameters for the grid [x,y,z] where x is the min of the grid, y is the
* Max of the grid and z is the number of points
* x: coordinates of the data
* y: coordinates of the data
* grid: data = [10,0,0,0,0,0,-10,30,25]; // the data values
*/
this.params = [0,10,11];
this.x = [3,7,2,8,0,10,3,7];
this.y = [0,0,3,3,8,8,10,10];
this.grid = [];
}

listen () {
this.signalEvent.on('bci:signal', (signal) => {
this.signalToGrid(signal);
this.emit();
});
}

signalToGrid (signal) {
let grid = [];
signal.forEach((channel) => {
grid.push(channel[channel.length - 1]);
});
this.grid = topogrid.create(this.x, this.y, grid, this.params);
}

emit () {
this.io.emit('bci:topo', {
data: this.grid
});
}

}
7 changes: 7 additions & 0 deletions src/server/providers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const Signals = require('./signals.provider');

module.exports = {
Signals
}
66 changes: 66 additions & 0 deletions src/server/providers/signals.provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

const Utils = require('../utils');
const EventEmitter = require('events');

class SignalEmitter extends EventEmitter {}

module.exports = class Signals {

constructor ({ connector, io }) {
this.connector = connector;
this.io = io;
this.signalEvent = new SignalEmitter();
this.bins = 512; // aka ~2 seconds
this.bufferSize = 512;
this.windowRefreshRate = 32;
this.windowSize = this.bins / this.windowRefreshRate;
this.sampleRate = this.connector.sampleRate(); // aka 250
this.sampleNumber = 0;
this.signals = [[],[],[],[],[],[],[],[]];
this.init();
}

init () {
// Sockets
this.io.on('connection', (socket) => {
console.log('A user connected');
socket.on('bci:filter', (filter) => {
Utils.filter.apply(filter);
});
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
}

buffer (sample) {
this.sampleNumber++;
this.add(sample);

if (this.sampleNumber === this.bins) {
this.signalEvent.emit('bci:signal', [...this.signals]); // clone array
this.window();
}
}

add (sample) {
//console.log('sample added', sample);
Object.keys(sample.channelData).forEach((channel, i) => {
this.signals[i].push(sample.channelData[channel]);
});
}

window () {
this.signals = this.signals.map((channel) => {
return channel.filter((signal, index) => {
return index > (this.windowSize - 1);
});
});
this.sampleNumber = this.bins - this.windowSize;
}

}



Loading

0 comments on commit 7661dad

Please sign in to comment.