UNPKG

@neurosity/sdk

Version:
72 lines (71 loc) 4.16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.epoch = exports.bufferToEpoch = exports.addInfo = void 0; const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const defaultDataProp = "data"; const defaultSamplingRate = 256; const isObject = (object) => object instanceof Object && object === Object(object); const isFunction = (object) => typeof object === "function"; const patch = (sample) => (info) => { var _a; return (Object.assign(Object.assign({}, sample), { info: Object.assign(Object.assign({}, ((_a = sample === null || sample === void 0 ? void 0 : sample.info) !== null && _a !== void 0 ? _a : {})), (info || {})) })); }; /** * Annotates stream with user-defined metadata * @method addInfo * @example eeg$.pipe(addinfo({ samplingRate: 256, channelNames: ["Af7", "Fp1", "Fp2", "Af8"] }) * @param {Object} info Info to be added to the EEG stream. Relevant info may include: `samplingRate` and `channelNames` * @returns {Observable<Sample|Epoch|PSD>} */ const addInfo = (infoValue) => (0, rxjs_1.pipe)((0, operators_1.map)((sample) => { if (!isObject(sample) || (!isObject(infoValue) && !isFunction(infoValue))) { return sample; } const info = isFunction(infoValue) ? infoValue(sample) : infoValue; return patch(sample)(info); })); exports.addInfo = addInfo; /** * Get a 2D data array organized by channel from an array of Samples. Credit to Ken from Seattle's elegant transposition * http://www.codesuck.com/2012/02/transpose-javascript-array-in-one-line.html * @method groupByChannel * @param {Array<Sample>} samplesBuffer Array of Samples to be grouped * @param {string} [dataProp] Name of the key associated with EEG data * @returns {Array<Array<number>>} */ const groupByChannel = (samplesBuffer, dataProp = defaultDataProp) => samplesBuffer[0][dataProp].map((_, channelIndex) => samplesBuffer.map((sample) => sample[dataProp][channelIndex])); /** * Takes an array or RxJS buffer of EEG Samples and returns an Epoch. * @method bufferToEpoch * @example eeg$.pipe(bufferTime(1000), bufferToEpoch({ samplingRate: 256 })) * * @param {Object} options - Data structure options * @param {number} [options.samplingRate] Sampling rate * @param {string} [options.dataProp='data'] Name of the key associated with eeg data * * @returns {Observable<Epoch>} */ const bufferToEpoch = ({ samplingRate = defaultSamplingRate, dataProp = defaultDataProp } = {}) => (0, rxjs_1.pipe)((0, operators_1.map)((samplesArray) => ({ [dataProp]: groupByChannel(samplesArray, dataProp), info: Object.assign(Object.assign({}, (samplesArray[0] && samplesArray[0].info ? samplesArray[0].info : {})), { startTime: samplesArray[0].timestamp, samplingRate: samplesArray[0].info && samplesArray[0].info.samplingRate ? samplesArray[0].info.samplingRate : samplingRate }) }))); exports.bufferToEpoch = bufferToEpoch; /** * Converts a stream of individual Samples of EEG data into a stream of Epochs of a given duration emitted at specified interval. This operator functions similarly to a circular buffer internally and allows overlapping Epochs of data to be emitted (e.g. emitting the last one second of data every 100ms). * @method epoch * @example eeg$.pipe(epoch({ duration: 1024, interval: 100, samplingRate: 256 })) * @param {Object} options - Epoching options * @param {number} [options.duration=256] Number of samples to include in each epoch * @param {number} [options.interval=100] Time (ms) between emitted Epochs * @param {number} [options.samplingRate=256] Sampling rate * @param {string} [options.dataProp='data'] Name of the key associated with eeg data * @returns {Observable} Epoch */ const epoch = ({ duration, interval, samplingRate, dataProp = defaultDataProp }) => (0, rxjs_1.pipe)((0, operators_1.bufferCount)(interval), (0, operators_1.scan)((acc, val) => acc.concat(val).slice(acc.length < duration ? 0 : -duration)), (0, operators_1.filter)((samplesArray) => samplesArray.length === duration), (0, exports.bufferToEpoch)({ samplingRate, dataProp })); exports.epoch = epoch;