Arquivos
2016-05-11 10:23:16 -04:00

303 linhas
8.7 KiB
JavaScript

var path = require('path');
var argv = require('yargs').argv;
var OpenBCIBoard = require('openbci-sdk');
var dsp = require('dsp.js');
var io = require('socket.io')(process.env.app_port || 8080);
var topogrid = require('topogrid');
var jStat = require('jstat').jStat;
var Fili = require('fili');
var globalScale = 1.5;
// Sockets
io.on('connection', function(socket){
console.log('A user connected');
});
// OpenBCI
var board = new OpenBCIBoard.OpenBCIBoard({
verbose: true
});
board.autoFindOpenBCIBoard()
.then(onBoardFind)
.catch(function () {
if (!!(argv._[0] && argv._[0] === 'simulate')) {
globalScale = 4;
board.connect(OpenBCIBoard.OpenBCIConstants.OBCISimulatorPortName)
.then(onBoardConnect);
}
});
// Board find handler
function onBoardFind (portName) {if (portName) {
console.log('board found', portName);
board.connect(portName)
.then(onBoardConnect);
}
}
// Board connect handler
function onBoardConnect () {
board.on('ready', onBoardReady);
}
// Board ready handler
function onBoardReady () {
board.streamStart();
board.on('sample', onSample);
}
var bins = 128; // Approx .5 second
var bufferSize = 128;
var windowRefreshRate = 8;
var windowSize = bins / windowRefreshRate;
var sampleRate = board.sampleRate();
var sampleInterval = (1 / sampleRate) * 1000; // in milliseconds (4)
var sampleNumber = 0;
var signals = [[],[],[],[],[],[],[],[]];
var timeSeriesWindow = 5; // in seconds
var timeSeriesRate = 10; // emits time series every 10 samples (adds 40 ms delay because this * sampleInterval = 40
var seriesNumber = 0;
var timeline = generateTimeline(20, 2, 's');
var timeSeries = new Array(8).fill([]); // 8 channels
timeSeries = timeSeries.map(function () {
return new Array((sampleRate * timeSeriesWindow)).fill(0).map(function (amplitude, channelNumber) {
return offsetForGrid(amplitude, channelNumber);
}); // / timeSeriesRate
});
// the 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
var grid_params = [0,10,11];
var pos_x = [3,7,2,8,0,10,3,7]; // x coordinates of the data
var pos_y = [0,0,3,3,8,8,10,10]; // y coordinates of the data
// var data = [10,0,0,0,0,0,-10,30,25]; // the data values
// Instance of a filter coefficient calculator
var iirCalculator = new Fili.CalcCascades();
// calculate filter coefficients
var notchFilterCoeffs = iirCalculator.bandstop({
order: 2, // cascade 3 biquad filters (max: 12)
characteristic: 'butterworth',
Fs: sampleRate, // sampling frequency
Fc: 60,
F1: 59,
F2: 61,
gain: 0, // gain for peak, lowshelf and highshelf
preGain: false // adds one constant multiplication for highpass and lowpass
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
});
// create a filter instance from the calculated coeffs
var notchFilter = new Fili.IirFilter(notchFilterCoeffs);
var hpFilterCoeffs = iirCalculator.highpass({
order: 3, // cascade 3 biquad filters (max: 12)
characteristic: 'butterworth',
Fs: sampleRate, // sampling frequency
Fc: 1,
gain: 0, // gain for peak, lowshelf and highshelf
preGain: false // adds one constant multiplication for highpass and lowpass
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
});
var hpFilter = new Fili.IirFilter(hpFilterCoeffs);
var lpFilterCoeffs = iirCalculator.lowpass({
order: 3, // cascade 3 biquad filters (max: 12)
characteristic: 'butterworth',
Fs: sampleRate, // sampling frequency
Fc: 50,
gain: 0, // gain for peak, lowshelf and highshelf
preGain: false // adds one constant multiplication for highpass and lowpass
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
});
var lpFilter = new Fili.IirFilter(lpFilterCoeffs);
function onSample (sample) {
sampleNumber++;
console.log('sample', sample);
Object.keys(sample.channelData).forEach(function (channel, i) {
signals[i].push(sample.channelData[channel]);
});
if (sampleNumber === bins) {
var spectrums = [[],[],[],[],[],[],[],[]];
signals.forEach(function (signal, index) {
signal = notchFilter.multiStep(signal);
var fft = new dsp.FFT(bufferSize, sampleRate);
fft.forward(signal);
spectrums[index] = parseObjectAsArray(fft.spectrum);
spectrums[index] = voltsToMicrovolts(spectrums[index], true);
});
var labels = new Array(bins / 2).fill()
// Apply scaler
.map(function (label, index) {
return Math.ceil(index * (sampleRate / bins));
});
var spectrumsByBand = [];
var bands = {
delta: [1, 3],
theta: [4, 8],
alpha: [8, 12],
beta: [13, 30],
gamma: [30, 100]
};
for (band in bands) {
spectrumsByBand[band] = filterBand(spectrums, labels, bands[band])
}
// Skip every 4, add unit
labels = labels.map(function (label, index, labels) {
return (index % 4 === 0 || index === (labels.length - 1)) ? label + ' Hz' : '';
});
io.emit('bci:fft', {
data: spectrums,
theta: spectrumsByBand.theta.spectrums,
delta: spectrumsByBand.delta.spectrums,
alpha: spectrumsByBand.alpha.spectrums,
beta: spectrumsByBand.beta.spectrums,
gamma: spectrumsByBand.gamma.spectrums,
labels: labels
});
signals = signals.map(function (channel) {
return channel.filter(function (signal, index) {
return index > (windowSize - 1);
});
});
var meanSpectrum = spectrums.map(function(channel){
return jStat.mean(channel);
});
sampleNumber = bins - windowSize;
}
// Time Series
seriesNumber++;
timeSeries.forEach(function (channel, index) {
channel.push(
offsetForGrid(sample.channelData[index], index)
);
channel.shift();
});
if (seriesNumber === timeSeriesRate) {
var amplitudes = signals.map(function (channel) {
return (Math.round(voltsToMicrovolts(channel[channel.length - 1])[0])) + ' uV';
});
io.emit('bci:time', {
data: timeSeries,
amplitudes: amplitudes,
timeline: timeline
});
seriesNumber = 0;
grid = topogrid.create(pos_x,pos_y,sample.channelData,grid_params);
var grid_flat = [].concat.apply([], grid);
io.emit('bci:topo', {
data: grid_flat
});
}
}
// @TODO: initial dataset is returning messed up values
function offsetForGrid (amplitude, channelNumber) {
var scaledAmplitude = amplitude * Math.pow(10, globalScale);
var offset = 2 * (timeSeries.length - channelNumber) - 1;
return parseFloat(scaledAmplitude + offset);
}
function voltsToMicrovolts (volts, log) {
if (!Array.isArray(volts)) volts = [volts];
return volts.map(function (volt) {
return log ? Math.log10(Math.pow(10, 6) * volt) : Math.pow(10, 6) * volt;
});
}
function parseObjectAsArray (obj) {
var array = [];
Object.keys(obj).forEach(function (key) {
array.push(obj[key]);
});
return array;
}
function filterBand(spectrums, labels, range) {
if (!spectrums ) return console.log('Please provide spectrums');
spectrums = spectrums.map(function (channel) {
return channel.filter(function (spectrum, index) {
return labels[index] >= range[0] && labels[index] <= range[1];
});
});
spectrums = [spectrums.map(function (channel) {
if (channel.length) {
return channel.reduce(function (a, b) {
return a + b;
}) / channel.length;
} else return channel;
})];
return {
spectrums: spectrums,
labels: labels
}
}
/**
* generateTimeline
* @param size
* @param skip
* @param suffix
* @returns {Array.<T>}
*/
function generateTimeline (size, skip, suffix) {
return new Array(size)
.fill()
.map(function (value, index) {
return index;
})
.filter(function (value, index) {
return index % skip === 0;
})
.map(function (value) {
return (value ? '-' : '') + value + suffix;
})
.reverse();
}
/**
* disconnectBoard
*/
function disconnectBoard () {
board.streamStop()
.then(function () {
board.disconnect().then(function () {
console.log('board disconnected');
process.exit();
});
});
}
process.on('SIGINT', disconnectBoard);