spectrum-generator
Advanced tools
Comparing version 1.1.0 to 2.0.0
@@ -0,1 +1,6 @@ | ||
<a name="2.0.0"></a> | ||
# [2.0.0](https://github.com/cheminfo/spectrum-generator/compare/v1.1.0...v2.0.0) (2018-07-28) | ||
<a name="1.1.0"></a> | ||
@@ -2,0 +7,0 @@ # [1.1.0](https://github.com/cheminfo/spectrum-generator/compare/v1.0.1...v1.1.0) (2018-03-12) |
344
lib/index.js
@@ -5,2 +5,69 @@ 'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var random = _interopDefault(require('random')); | ||
var seedrandom = _interopDefault(require('seedrandom')); | ||
/** | ||
* Add a baseline to the spectrum | ||
* @param {object} [data] - Your spectrum data in the format {x:[x1, x2, ...], y:[y1, y2, ...]} | ||
* @param {function} [baselineFct] - Mathematical function producing the baseline you want | ||
* @return {object} data | ||
*/ | ||
function addBaseline(data, baselineFct) { | ||
if (!baselineFct) return data; | ||
var xs = data.x; | ||
var ys = data.y; | ||
for (let i = 0; i < xs.length; i++) { | ||
ys[i] += baselineFct(xs[i]); | ||
} | ||
return data; | ||
} | ||
/** | ||
* Add noise to the spectrum | ||
* @param {object} [data] - Your spectrum data in the format {x:[x1, x2, ...], y:[y1, y2, ...]} | ||
* @param {number} [percent = 0] - Noise's amplitude in percents of the spectrum max value | ||
* @param {object} [options={}] | ||
* @param {boolean} [options.seed=false] - Deterministic sequence of random number | ||
* @param {string} [options.distribution='uniform'] -Type of random: 'uniform' (true random), 'normal' (gaussian distribution), | ||
* @return {data} data | ||
*/ | ||
function addNoise(data, percent = 0, options = {}) { | ||
const { | ||
distribution = 'uniform', | ||
seed = false | ||
} = options; | ||
if (seed) random.use(seedrandom(0)); | ||
let generateRandomNumber; | ||
switch (distribution) { | ||
case 'uniform': | ||
generateRandomNumber = random.uniform(-0.5, 0.5); | ||
break; | ||
case 'normal': | ||
generateRandomNumber = random.normal(); | ||
break; | ||
default: | ||
throw new Error(`Unknown distribution ${options.distribution}`); | ||
} | ||
if (!percent) return data; | ||
var ys = data.y; | ||
var factor = percent * findMax(ys) / 100; | ||
for (let i = 0; i < ys.length; i++) { | ||
ys[i] += generateRandomNumber() * factor; | ||
} | ||
return data; | ||
} | ||
function findMax(array) { | ||
let max = Number.MIN_VALUE; | ||
for (let item of array) { | ||
if (item > max) max = item; | ||
} | ||
return max; | ||
} | ||
const gaussianFactor = 5; // after 5 the value is nearly 0, nearly no artifacts | ||
@@ -11,19 +78,8 @@ const gaussianWidth = 1000; // half height peak Width in point | ||
for (let i = 0; i <= gaussianWidth * gaussianFactor; i++) { | ||
gaussian.push(Math.exp(-1 / 2 * Math.pow((i - (gaussianFactor * gaussianWidth / 2)) * 2 / gaussianWidth * ratio, 2))); | ||
gaussian.push(Math.exp(-1 / 2 * Math.pow((i - (gaussianFactor * gaussianWidth / 2)) * 2 / gaussianWidth * ratio, 2))); | ||
} | ||
function defaultGetWidth(value) { | ||
return 1 + 3 * value / 1000; | ||
} | ||
const kStart = Symbol('start'); | ||
const kEnd = Symbol('end'); | ||
const kPointsPerUnit = Symbol('pointsPerUnit'); | ||
const kGetWidth = Symbol('getWidth'); | ||
const kSize = Symbol('size'); | ||
const kSpectrum = Symbol('spectrum'); | ||
const kMaxSize = Symbol('maxSize'); | ||
class SpectrumGenerator { | ||
/** | ||
/** | ||
* @class SpectrumGenerator | ||
@@ -34,39 +90,67 @@ * @constructor | ||
* @param {number} [options.end=1000] - Last x value (inclusive) | ||
* @param {function} [options.peakWidthFct=function(x){return(5)}] - Width of peak depending the x value | ||
* @param {number} [options.pointsPerUnit=5] - Number of values between each unit of the x axis | ||
* @param {number} [options.maxSize=1e7] - maximal array size | ||
* @param {function} [options.getWidth] - Returns the width of a peak for a given value. Defaults to (1 + 3 * value / 1000) | ||
* | ||
* @example | ||
* import SG from 'spectrum-generator'; | ||
* const sg = new SG({start: 0, end: 100, pointsPerUnit: 5, peakWidthFct: (x) => 1 + 3 * x / 1000 }); | ||
* sg.addPeak( [5, 50] ); | ||
* sg.addPeak([20, 100], { width: 3 }); | ||
* sg.addPeak([35, 100], { widthLeft: 10, widthRight: 30 }); | ||
* sg.addPeak([50, 10], { widthLeft: 5, widthRight: 5 }); | ||
* sg.addPeaks([ [70,20], [80,40], [90,10] ]); | ||
* sg.addNoise(10); | ||
* sg.addBaseline( (x) => x * x / 100 ); | ||
* var spectrum = sg.getSpectrum(); | ||
* | ||
* @example | ||
* import SG from 'spectrum-generator'; | ||
* const spectrum=SG.generateSpectrum([ [20,3], [30,2], [40,2] ], { | ||
* start: 0, | ||
* end: 100, | ||
* pointsPerUnit: 1, | ||
* noise: { | ||
* percent: 10, | ||
* distribution: 'normal', | ||
* seed: true | ||
* }, | ||
* baseline: (x) => 2 * x | ||
* }) | ||
*/ | ||
constructor(options = {}) { | ||
const { | ||
start = 0, | ||
end = 1000, | ||
pointsPerUnit = 5, | ||
getWidth = defaultGetWidth, | ||
maxSize = 1e7 | ||
} = options; | ||
constructor(options = {}) { | ||
options = Object.assign({}, { | ||
start: 0, | ||
end: 1000, | ||
pointsPerUnit: 5, | ||
peakWidthFct: (() => 5), | ||
maxSize: 1e7 | ||
}, options); | ||
this.start = options.start; | ||
this.end = options.end; | ||
this.pointsPerUnit = options.pointsPerUnit; | ||
this.peakWidthFct = options.peakWidthFct; | ||
this.maxSize = options.maxSize; | ||
assertInteger(start, 'start'); | ||
assertInteger(end, 'end'); | ||
assertInteger(pointsPerUnit, 'pointsPerUnit'); | ||
assertInteger(maxSize, 'maxSize'); | ||
assertInteger(this.start, 'start'); | ||
assertInteger(this.end, 'end'); | ||
assertInteger(this.pointsPerUnit, 'pointsPerUnit'); | ||
assertInteger(this.maxSize, 'maxSize'); | ||
if (end <= start) { | ||
throw new RangeError('end option must be larger than start'); | ||
} | ||
if (this.end <= this.start) { | ||
throw new RangeError('end option must be larger than start'); | ||
} | ||
if (typeof getWidth !== 'function') { | ||
throw new TypeError('getWidth option must be a function'); | ||
} | ||
if (typeof this.peakWidthFct !== 'function') { | ||
throw new TypeError('peakWidthFct option must be a function'); | ||
} | ||
this[kStart] = start; | ||
this[kEnd] = end; | ||
this[kPointsPerUnit] = pointsPerUnit; | ||
this[kGetWidth] = getWidth; | ||
this[kMaxSize] = maxSize; | ||
this[kSize] = (end - start) * pointsPerUnit + 1; | ||
this.reset(); | ||
} | ||
this.reset(maxSize); | ||
} | ||
get size() { | ||
return (this.end - this.start) * this.pointsPerUnit + 1; | ||
} | ||
/** | ||
/** | ||
* Add a series of peaks to the spectrum. | ||
@@ -76,46 +160,79 @@ * @param {Array<Array<number>>} peaks | ||
*/ | ||
addPeaks(peaks) { | ||
if (!Array.isArray(peaks)) { | ||
throw new TypeError('peaks must be an array'); | ||
} | ||
for (const peak of peaks) { | ||
this.addPeak(peak); | ||
} | ||
return this; | ||
addPeaks(peaks) { | ||
if (!Array.isArray(peaks)) { | ||
throw new TypeError('peaks must be an array'); | ||
} | ||
for (const peak of peaks) { | ||
this.addPeak(peak); | ||
} | ||
return this; | ||
} | ||
/** | ||
/** | ||
* Add a single peak to the spectrum. | ||
* @param {Array<number>} peak | ||
* @param {object} [options={}] | ||
* @param {number} [options.width] Half-height width | ||
* @param {number} [options.widthLeft] Half-height width left (asymmetric peak) | ||
* @param {number} [options.widthRight] Half-height width right (asymmetric peak) | ||
* @return {this} | ||
*/ | ||
addPeak(peak) { | ||
if (!Array.isArray(peak) || peak.length !== 2) { | ||
throw new Error('peak must be an array with two values'); | ||
} | ||
addPeak(peak, options = {}) { | ||
if (!Array.isArray(peak) || peak.length !== 2) { | ||
throw new Error('peak must be an array with two values'); | ||
} | ||
const value = peak[0]; | ||
const intensity = peak[1]; | ||
const width = this[kGetWidth](value); | ||
const firstValue = value - (width / 2 * gaussianFactor); | ||
const lastValue = value + (width / 2 * gaussianFactor); | ||
const value = peak[0]; | ||
const intensity = peak[1]; | ||
const firstPoint = Math.floor(firstValue * this[kPointsPerUnit]); | ||
const lastPoint = Math.ceil(lastValue * this[kPointsPerUnit]); | ||
const middlePoint = (firstPoint + lastPoint) / 2; | ||
let { | ||
width = this.peakWidthFct(value), | ||
widthLeft, | ||
widthRight | ||
} = options; | ||
for (var j = firstPoint; j <= lastPoint; j++) { | ||
var index = j - this[kStart] * this[kPointsPerUnit]; | ||
if (index >= 0 && index < this[kSize]) { | ||
var gaussianIndex = Math.floor(gaussianWidth / width * (j - middlePoint) / this[kPointsPerUnit] + gaussianFactor * gaussianWidth / 2); | ||
if (gaussianIndex >= 0 && gaussianIndex < gaussian.length) { | ||
this[kSpectrum].y[index] += gaussian[gaussianIndex] * intensity; | ||
} | ||
} | ||
if (!widthLeft) widthLeft = width; | ||
if (!widthRight) widthRight = width; | ||
const firstValue = value - (widthLeft / 2 * gaussianFactor); | ||
const lastValue = value + (widthRight / 2 * gaussianFactor); | ||
const firstPoint = Math.floor(firstValue * this.pointsPerUnit); | ||
const lastPoint = Math.ceil(lastValue * this.pointsPerUnit); | ||
const middlePoint = value * this.pointsPerUnit; | ||
// we calculate the left part of the gaussian | ||
for (let j = firstPoint; j < middlePoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
if (index >= 0 && index < this.size) { | ||
let gaussianIndex = Math.floor(gaussianWidth / widthLeft * (j - middlePoint) / this.pointsPerUnit + gaussianFactor * gaussianWidth / 2); | ||
if (gaussianIndex >= 0 && gaussianIndex < gaussian.length) { | ||
this.data.y[index] += gaussian[gaussianIndex] * intensity; | ||
} | ||
} | ||
} | ||
return this; | ||
// we calculate the right part of the gaussian | ||
for (let j = middlePoint; j <= lastPoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
if (index >= 0 && index < this.size) { | ||
let gaussianIndex = Math.floor(gaussianWidth / widthRight * (j - middlePoint) / this.pointsPerUnit + gaussianFactor * gaussianWidth / 2); | ||
if (gaussianIndex >= 0 && gaussianIndex < gaussian.length) { | ||
this.data.y[index] += gaussian[gaussianIndex] * intensity; | ||
} | ||
} | ||
} | ||
/** | ||
return this; | ||
} | ||
addBaseline(baselineFct) { | ||
addBaseline(this.data, baselineFct); | ||
} | ||
addNoise(percent, options) { | ||
addNoise(this.data, percent, options); | ||
} | ||
/** | ||
* Get the generated spectrum. | ||
@@ -126,52 +243,49 @@ * @param {boolean} [copy=true] - If true, returns a copy of the spectrum. | ||
*/ | ||
getSpectrum(copy = true) { | ||
if (copy) { | ||
return { | ||
x: this[kSpectrum].x.slice(), | ||
y: this[kSpectrum].y.slice() | ||
}; | ||
} else { | ||
return this[kSpectrum]; | ||
} | ||
getSpectrum(copy = true) { | ||
if (copy) { | ||
return { | ||
x: this.data.x.slice(), | ||
y: this.data.y.slice() | ||
}; | ||
} else { | ||
return this.data; | ||
} | ||
} | ||
/** | ||
/** | ||
* Resets the generator with an empty spectrum. | ||
* @return {this} | ||
*/ | ||
reset() { | ||
let finalSize = (this[kEnd] - this[kStart]) * this[kPointsPerUnit] + 1; | ||
if (finalSize > this[kMaxSize]) { | ||
throw new Error('Generated array has size ' + finalSize + ' larger than maxSize: ' + this[kMaxSize]); | ||
} | ||
reset() { | ||
if (this.size > this.maxSize) { | ||
throw new Error(`Generated array has size ${this.size} larger than maxSize: ${this.maxSize}`); | ||
} | ||
const spectrum = this[kSpectrum] = { | ||
x: [], | ||
y: [] | ||
}; | ||
const spectrum = this.data = { | ||
x: [], | ||
y: new Array(this.size).fill(0) | ||
}; | ||
const interval = 1 / this[kPointsPerUnit]; | ||
const js = []; | ||
for (let j = 0; j < this[kPointsPerUnit]; j++) { | ||
js.push(j * interval); | ||
} | ||
const interval = 1 / this.pointsPerUnit; | ||
const js = []; | ||
for (let j = 0; j < this.pointsPerUnit; j++) { | ||
js.push(j * interval); | ||
} | ||
for (let i = this[kStart]; i < this[kEnd]; i++) { | ||
for (let j = 0; j < this[kPointsPerUnit]; j++) { | ||
spectrum.x.push(i + js[j]); | ||
spectrum.y.push(0); | ||
} | ||
} | ||
for (let i = this.start; i < this.end; i++) { | ||
for (let j = 0; j < this.pointsPerUnit; j++) { | ||
spectrum.x.push(i + js[j]); | ||
} | ||
} | ||
spectrum.x.push(this[kEnd]); | ||
spectrum.y.push(0); | ||
spectrum.x.push(this.end); | ||
return this; | ||
} | ||
return this; | ||
} | ||
} | ||
function assertInteger(value, name) { | ||
if (!Number.isInteger(value)) { | ||
throw new TypeError(`${name} option must be an integer`); | ||
} | ||
if (!Number.isInteger(value)) { | ||
throw new TypeError(`${name} option must be an integer`); | ||
} | ||
} | ||
@@ -185,9 +299,11 @@ | ||
*/ | ||
function generateSpectrum(peaks, options) { | ||
const generator = new SpectrumGenerator(options); | ||
generator.addPeaks(peaks); | ||
return generator.getSpectrum(); | ||
function generateSpectrum(peaks, options = {}) { | ||
const generator = new SpectrumGenerator(options); | ||
generator.addPeaks(peaks); | ||
if (options.baseline) generator.addBaseline(options.baseline); | ||
if (options.noise) generator.addNoise(options.noise.percent, options.noise); | ||
return generator.getSpectrum(); | ||
} | ||
exports.SpectrumGenerator = SpectrumGenerator; | ||
exports['default'] = SpectrumGenerator; | ||
exports.generateSpectrum = generateSpectrum; |
{ | ||
"name": "spectrum-generator", | ||
"version": "1.1.0", | ||
"version": "2.0.0", | ||
"description": "generate a spectrum from discrete peaks", | ||
@@ -26,3 +26,4 @@ "main": "lib/index.js", | ||
"contributors": [ | ||
"Michaël Zasso" | ||
"Michaël Zasso", | ||
"Océane Patiny" | ||
], | ||
@@ -42,7 +43,15 @@ "license": "MIT", | ||
"eslint-config-cheminfo": "^1.8.0", | ||
"eslint-plugin-import": "^2.9.0", | ||
"eslint-plugin-jest": "^21.15.0", | ||
"eslint-plugin-no-only-tests": "^2.0.0", | ||
"esm": "^3.0.69", | ||
"jest": "^21.2.1", | ||
"npm-run-all": "^4.1.2", | ||
"rollup": "^0.51.0" | ||
}, | ||
"dependencies": { | ||
"debug": "^3.1.0", | ||
"random": "^2.0.12", | ||
"seedrandom": "^2.4.3" | ||
} | ||
} |
@@ -1,6 +0,6 @@ | ||
import {SpectrumGenerator} from '..'; | ||
import SpectrumGenerator from '..'; | ||
const integerReg = /^\w+ option must be an integer$/; | ||
const endStartReg = /^end option must be larger than start$/; | ||
const getWidthReg = /^getWidth option must be a function$/; | ||
const peakWidthReg = /^peakWidthFct option must be a function$/; | ||
const addPeaksReg = /^peaks must be an array$/; | ||
@@ -10,33 +10,33 @@ const addPeakReg = /^peak must be an array with two values$/; | ||
describe('errors', () => { | ||
it('wrong options', () => { | ||
expect(() => new SpectrumGenerator({start: 0.5})).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({start: false})).toThrow(integerReg); | ||
it('wrong options', () => { | ||
expect(() => new SpectrumGenerator({ start: 0.5 })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ start: false })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({end: 0.5})).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({end: false})).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ end: 0.5 })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ end: false })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({pointsPerUnit: 0.5})).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({pointsPerUnit: false})).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ pointsPerUnit: 0.5 })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ pointsPerUnit: false })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({start: 0, end: 0})).toThrow(endStartReg); | ||
expect(() => new SpectrumGenerator({start: 0, end: -10})).toThrow(endStartReg); | ||
expect(() => new SpectrumGenerator({ start: 0, end: 0 })).toThrow(endStartReg); | ||
expect(() => new SpectrumGenerator({ start: 0, end: -10 })).toThrow(endStartReg); | ||
expect(() => new SpectrumGenerator({getWidth: null})).toThrow(getWidthReg); | ||
}); | ||
expect(() => new SpectrumGenerator({ peakWidthFct: null })).toThrow(peakWidthReg); | ||
}); | ||
it('addPeaks not an array', () => { | ||
const generator = new SpectrumGenerator(); | ||
expect(() => generator.addPeaks()).toThrow(addPeaksReg); | ||
expect(() => generator.addPeaks({})).toThrow(addPeaksReg); | ||
}); | ||
it('addPeaks not an array', () => { | ||
const generator = new SpectrumGenerator(); | ||
expect(() => generator.addPeaks()).toThrow(addPeaksReg); | ||
expect(() => generator.addPeaks({})).toThrow(addPeaksReg); | ||
}); | ||
it('addPeak not an array', () => { | ||
const generator = new SpectrumGenerator(); | ||
expect(() => generator.addPeak()).toThrow(addPeakReg); | ||
expect(() => generator.addPeak({})).toThrow(addPeakReg); | ||
expect(() => generator.addPeak({})).toThrow(addPeakReg); | ||
expect(() => generator.addPeak([])).toThrow(addPeakReg); | ||
expect(() => generator.addPeak([1])).toThrow(addPeakReg); | ||
expect(() => generator.addPeak([1, 2, 3])).toThrow(addPeakReg); | ||
}); | ||
it('addPeak not an array', () => { | ||
const generator = new SpectrumGenerator(); | ||
expect(() => generator.addPeak()).toThrow(addPeakReg); | ||
expect(() => generator.addPeak({})).toThrow(addPeakReg); | ||
expect(() => generator.addPeak({})).toThrow(addPeakReg); | ||
expect(() => generator.addPeak([])).toThrow(addPeakReg); | ||
expect(() => generator.addPeak([1])).toThrow(addPeakReg); | ||
expect(() => generator.addPeak([1, 2, 3])).toThrow(addPeakReg); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
import {generateSpectrum} from '..'; | ||
import { generateSpectrum } from '..'; | ||
@@ -6,86 +6,83 @@ const simpleGetWidth = () => 1; | ||
describe('generateSpectrum', () => { | ||
it('should work from zero', () => { | ||
assertSimple({ | ||
start: 0, | ||
end: 10, | ||
peak: 5 | ||
}); | ||
it('should work from zero', () => { | ||
assertSimple({ | ||
start: 0, | ||
end: 10, | ||
peak: 5 | ||
}); | ||
}); | ||
it('should work from positive start', () => { | ||
assertSimple({ | ||
start: 5, | ||
end: 15, | ||
peak: 10 | ||
}); | ||
it('should work from positive start', () => { | ||
assertSimple({ | ||
start: 5, | ||
end: 15, | ||
peak: 10 | ||
}); | ||
}); | ||
it('should work from negative start', () => { | ||
assertSimple({ | ||
start: -15, | ||
end: -5, | ||
peak: -10 | ||
}); | ||
it('should work from negative start', () => { | ||
assertSimple({ | ||
start: -15, | ||
end: -5, | ||
peak: -10 | ||
}); | ||
}); | ||
}); | ||
describe('generateSpectrum with one peak and small window', () => { | ||
it('should work from 11', () => { | ||
const spectrum = generateSpectrum([[12, 1]], { | ||
start: 11, | ||
end: 13, | ||
pointsPerUnit: 10, | ||
getWidth: () => 0.1 | ||
}); | ||
expect(Math.max(...spectrum.y)).toBe(1); | ||
it('should work from 11', () => { | ||
const spectrum = generateSpectrum([[12, 1]], { | ||
start: 11, | ||
end: 13, | ||
pointsPerUnit: 10, | ||
getWidth: () => 0.1 | ||
}); | ||
expect(Math.max(...spectrum.y)).toBe(1); | ||
}); | ||
}); | ||
describe('generateSpectrum check large size', () => { | ||
let data = []; | ||
for (let i = 0; i < 10000; i++) { | ||
data.push([i, Math.random()]); | ||
} | ||
it('should throw error for huge array', () => { | ||
expect(() => generateSpectrum(data, { | ||
start: 0, | ||
end: 10000, | ||
pointsPerUnit: 1000, | ||
getWidth: () => 0.1 | ||
})).toThrow('Generated array has size 10000001 larger than maxSize: 10000000'); | ||
}); | ||
let data = []; | ||
for (let i = 0; i < 10000; i++) { | ||
data.push([i, Math.random()]); | ||
} | ||
it('should throw error for huge array', () => { | ||
expect(() => generateSpectrum(data, { | ||
start: 0, | ||
end: 10000, | ||
pointsPerUnit: 1000, | ||
getWidth: () => 0.1 | ||
})).toThrow('Generated array has size 10000001 larger than maxSize: 10000000'); | ||
}); | ||
it('should throw for simple array is maxSize=1', () => { | ||
expect(() => generateSpectrum([[1, 1]], { | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 1, | ||
maxSize: 1, | ||
getWidth: () => 0.1 | ||
})).toThrow('Generated array has size 3 larger than maxSize: 1'); | ||
}); | ||
it('should throw for simple array is maxSize=1', () => { | ||
expect(() => generateSpectrum([[1, 1]], { | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 1, | ||
maxSize: 1, | ||
getWidth: () => 0.1 | ||
})).toThrow('Generated array has size 3 larger than maxSize: 1'); | ||
}); | ||
}); | ||
function assertSimple({start, end, peak}) { | ||
const spectrum = generateSpectrum([[peak, 1]], {start, end, pointsPerUnit: 1, getWidth: simpleGetWidth}); | ||
assertSize(spectrum, end - start + 1); | ||
assertInterval(spectrum, start); | ||
function assertSimple({ start, end, peak }) { | ||
const spectrum = generateSpectrum([[peak, 1]], { start, end, pointsPerUnit: 1, getWidth: simpleGetWidth }); | ||
assertSize(spectrum, end - start + 1); | ||
assertInterval(spectrum, start); | ||
} | ||
function assertSize(spectrum, size) { | ||
expect(spectrum.x.length).toBe(size); | ||
expect(spectrum.y.length).toBe(size); | ||
expect(spectrum.x).toHaveLength(size); | ||
expect(spectrum.y).toHaveLength(size); | ||
} | ||
function assertInterval(spectrum, start) { | ||
let expected = start; | ||
for (const value of spectrum.x) { | ||
expect(value).toBe(expected); | ||
expected++; | ||
} | ||
let expected = start; | ||
for (const value of spectrum.x) { | ||
expect(value).toBe(expected); | ||
expected++; | ||
} | ||
} | ||
@@ -1,92 +0,102 @@ | ||
import {SpectrumGenerator} from '..'; | ||
import SpectrumGenerator from '..'; | ||
describe('SpectrumGenerator', () => { | ||
it('0 half peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5 | ||
}); | ||
it('0 half peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5 | ||
}); | ||
generator.addPeak([0, 1]); | ||
generator.addPeak([0, 1]); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 0, 1); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 0, 1); | ||
}); | ||
it('end half peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5 | ||
}); | ||
it('end half peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5 | ||
}); | ||
generator.addPeak([2, 1]); | ||
generator.addPeak([2, 1]); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 2 * 5, 1); | ||
}); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 2 * 5, 1); | ||
it('1 middle peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5 | ||
}); | ||
it('1 middle peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5 | ||
}); | ||
generator.addPeak([1, 1]); | ||
generator.addPeak([1, 1]); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 1 * 5, 1); | ||
}); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 1 * 5, 1); | ||
it('check asymmetric peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 100, | ||
pointsPerUnit: 2 | ||
}); | ||
generator.addPeak([35, 100], { widthLeft: 10, widthRight: 30 }); | ||
expect(generator.getSpectrum()).toMatchSnapshot(); | ||
}); | ||
it('1 middle peak check width', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 10 | ||
}); | ||
it('1 middle peak check width', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 10, | ||
peakWidthFct: (x) => 1 + 3 * x / 1000 | ||
}); | ||
generator.addPeak([1, 1]); | ||
generator.addPeak([1, 1]); | ||
const spectrum = generator.getSpectrum(); | ||
expect(spectrum.y[ 0.5 * 10]).toBeCloseTo(0.5, 2); | ||
expect(spectrum.y[ 1.5 * 10]).toBeCloseTo(0.5, 2); | ||
expectValue(spectrum, 1 * 10, 1); | ||
}); | ||
const spectrum = generator.getSpectrum(); | ||
expect(spectrum.y[0.5 * 10]).toBeCloseTo(0.5, 2); | ||
expect(spectrum.y[1.5 * 10]).toBeCloseTo(0.5, 2); | ||
expectValue(spectrum, 1 * 10, 1); | ||
}); | ||
it('full generation', () => { | ||
const generator = new SpectrumGenerator(); | ||
it('full generation', () => { | ||
const generator = new SpectrumGenerator(); | ||
generator.addPeak([0, 1]); | ||
generator.addPeak([50, 12]); | ||
generator.addPeaks([[100, 10], [14, 2]]); | ||
generator.addPeak([0, 1]); | ||
generator.addPeak([50, 12]); | ||
generator.addPeaks([[100, 10], [14, 2]]); | ||
const spectrum = generator.getSpectrum(); | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 0, 1); | ||
expectValue(spectrum, 50 * 5, 12); | ||
expectValue(spectrum, 100 * 5, 10); | ||
expectValue(spectrum, 14 * 5, 2); | ||
expectValue(spectrum, 0, 1); | ||
expectValue(spectrum, 50 * 5, 12); | ||
expectValue(spectrum, 100 * 5, 10); | ||
expectValue(spectrum, 14 * 5, 2); | ||
}); | ||
}); | ||
it('getSpectrum', () => { | ||
const generator = new SpectrumGenerator(); | ||
it('getSpectrum', () => { | ||
const generator = new SpectrumGenerator(); | ||
const s1 = generator.getSpectrum(); | ||
const s2 = generator.getSpectrum(); | ||
const s1 = generator.getSpectrum(); | ||
const s2 = generator.getSpectrum(); | ||
expect(s1).not.toBe(s2); | ||
expect(s1).not.toBe(s2); | ||
const s3 = generator.getSpectrum(false); | ||
const s4 = generator.getSpectrum(false); | ||
const s3 = generator.getSpectrum(false); | ||
const s4 = generator.getSpectrum(false); | ||
expect(s3).toBe(s4); | ||
expect(s3).not.toBe(s2); | ||
}); | ||
expect(s3).toBe(s4); | ||
expect(s3).not.toBe(s2); | ||
}); | ||
}); | ||
function expectValue(spectrum, index, value) { | ||
expect(spectrum.y[index]).toBe(value); | ||
expect(spectrum.y[index]).toBe(value); | ||
} |
281
src/index.js
@@ -0,1 +1,5 @@ | ||
import addBaseline from './util/addBaseline.js'; | ||
import addNoise from './util/addNoise.js'; | ||
const gaussianFactor = 5; // after 5 the value is nearly 0, nearly no artifacts | ||
@@ -6,19 +10,8 @@ const gaussianWidth = 1000; // half height peak Width in point | ||
for (let i = 0; i <= gaussianWidth * gaussianFactor; i++) { | ||
gaussian.push(Math.exp(-1 / 2 * Math.pow((i - (gaussianFactor * gaussianWidth / 2)) * 2 / gaussianWidth * ratio, 2))); | ||
gaussian.push(Math.exp(-1 / 2 * Math.pow((i - (gaussianFactor * gaussianWidth / 2)) * 2 / gaussianWidth * ratio, 2))); | ||
} | ||
function defaultGetWidth(value) { | ||
return 1 + 3 * value / 1000; | ||
} | ||
const kStart = Symbol('start'); | ||
const kEnd = Symbol('end'); | ||
const kPointsPerUnit = Symbol('pointsPerUnit'); | ||
const kGetWidth = Symbol('getWidth'); | ||
const kSize = Symbol('size'); | ||
const kSpectrum = Symbol('spectrum'); | ||
const kMaxSize = Symbol('maxSize'); | ||
export class SpectrumGenerator { | ||
/** | ||
export default class SpectrumGenerator { | ||
/** | ||
* @class SpectrumGenerator | ||
@@ -29,39 +22,67 @@ * @constructor | ||
* @param {number} [options.end=1000] - Last x value (inclusive) | ||
* @param {function} [options.peakWidthFct=function(x){return(5)}] - Width of peak depending the x value | ||
* @param {number} [options.pointsPerUnit=5] - Number of values between each unit of the x axis | ||
* @param {number} [options.maxSize=1e7] - maximal array size | ||
* @param {function} [options.getWidth] - Returns the width of a peak for a given value. Defaults to (1 + 3 * value / 1000) | ||
* | ||
* @example | ||
* import SG from 'spectrum-generator'; | ||
* const sg = new SG({start: 0, end: 100, pointsPerUnit: 5, peakWidthFct: (x) => 1 + 3 * x / 1000 }); | ||
* sg.addPeak( [5, 50] ); | ||
* sg.addPeak([20, 100], { width: 3 }); | ||
* sg.addPeak([35, 100], { widthLeft: 10, widthRight: 30 }); | ||
* sg.addPeak([50, 10], { widthLeft: 5, widthRight: 5 }); | ||
* sg.addPeaks([ [70,20], [80,40], [90,10] ]); | ||
* sg.addNoise(10); | ||
* sg.addBaseline( (x) => x * x / 100 ); | ||
* var spectrum = sg.getSpectrum(); | ||
* | ||
* @example | ||
* import SG from 'spectrum-generator'; | ||
* const spectrum=SG.generateSpectrum([ [20,3], [30,2], [40,2] ], { | ||
* start: 0, | ||
* end: 100, | ||
* pointsPerUnit: 1, | ||
* noise: { | ||
* percent: 10, | ||
* distribution: 'normal', | ||
* seed: true | ||
* }, | ||
* baseline: (x) => 2 * x | ||
* }) | ||
*/ | ||
constructor(options = {}) { | ||
const { | ||
start = 0, | ||
end = 1000, | ||
pointsPerUnit = 5, | ||
getWidth = defaultGetWidth, | ||
maxSize = 1e7 | ||
} = options; | ||
constructor(options = {}) { | ||
options = Object.assign({}, { | ||
start: 0, | ||
end: 1000, | ||
pointsPerUnit: 5, | ||
peakWidthFct: (() => 5), | ||
maxSize: 1e7 | ||
}, options); | ||
this.start = options.start; | ||
this.end = options.end; | ||
this.pointsPerUnit = options.pointsPerUnit; | ||
this.peakWidthFct = options.peakWidthFct; | ||
this.maxSize = options.maxSize; | ||
assertInteger(start, 'start'); | ||
assertInteger(end, 'end'); | ||
assertInteger(pointsPerUnit, 'pointsPerUnit'); | ||
assertInteger(maxSize, 'maxSize'); | ||
assertInteger(this.start, 'start'); | ||
assertInteger(this.end, 'end'); | ||
assertInteger(this.pointsPerUnit, 'pointsPerUnit'); | ||
assertInteger(this.maxSize, 'maxSize'); | ||
if (end <= start) { | ||
throw new RangeError('end option must be larger than start'); | ||
} | ||
if (this.end <= this.start) { | ||
throw new RangeError('end option must be larger than start'); | ||
} | ||
if (typeof getWidth !== 'function') { | ||
throw new TypeError('getWidth option must be a function'); | ||
} | ||
if (typeof this.peakWidthFct !== 'function') { | ||
throw new TypeError('peakWidthFct option must be a function'); | ||
} | ||
this[kStart] = start; | ||
this[kEnd] = end; | ||
this[kPointsPerUnit] = pointsPerUnit; | ||
this[kGetWidth] = getWidth; | ||
this[kMaxSize] = maxSize; | ||
this[kSize] = (end - start) * pointsPerUnit + 1; | ||
this.reset(); | ||
} | ||
this.reset(maxSize); | ||
} | ||
get size() { | ||
return (this.end - this.start) * this.pointsPerUnit + 1; | ||
} | ||
/** | ||
/** | ||
* Add a series of peaks to the spectrum. | ||
@@ -71,46 +92,79 @@ * @param {Array<Array<number>>} peaks | ||
*/ | ||
addPeaks(peaks) { | ||
if (!Array.isArray(peaks)) { | ||
throw new TypeError('peaks must be an array'); | ||
} | ||
for (const peak of peaks) { | ||
this.addPeak(peak); | ||
} | ||
return this; | ||
addPeaks(peaks) { | ||
if (!Array.isArray(peaks)) { | ||
throw new TypeError('peaks must be an array'); | ||
} | ||
for (const peak of peaks) { | ||
this.addPeak(peak); | ||
} | ||
return this; | ||
} | ||
/** | ||
/** | ||
* Add a single peak to the spectrum. | ||
* @param {Array<number>} peak | ||
* @param {object} [options={}] | ||
* @param {number} [options.width] Half-height width | ||
* @param {number} [options.widthLeft] Half-height width left (asymmetric peak) | ||
* @param {number} [options.widthRight] Half-height width right (asymmetric peak) | ||
* @return {this} | ||
*/ | ||
addPeak(peak) { | ||
if (!Array.isArray(peak) || peak.length !== 2) { | ||
throw new Error('peak must be an array with two values'); | ||
} | ||
addPeak(peak, options = {}) { | ||
if (!Array.isArray(peak) || peak.length !== 2) { | ||
throw new Error('peak must be an array with two values'); | ||
} | ||
const value = peak[0]; | ||
const intensity = peak[1]; | ||
const width = this[kGetWidth](value); | ||
const firstValue = value - (width / 2 * gaussianFactor); | ||
const lastValue = value + (width / 2 * gaussianFactor); | ||
const value = peak[0]; | ||
const intensity = peak[1]; | ||
const firstPoint = Math.floor(firstValue * this[kPointsPerUnit]); | ||
const lastPoint = Math.ceil(lastValue * this[kPointsPerUnit]); | ||
const middlePoint = (firstPoint + lastPoint) / 2; | ||
let { | ||
width = this.peakWidthFct(value), | ||
widthLeft, | ||
widthRight | ||
} = options; | ||
for (var j = firstPoint; j <= lastPoint; j++) { | ||
var index = j - this[kStart] * this[kPointsPerUnit]; | ||
if (index >= 0 && index < this[kSize]) { | ||
var gaussianIndex = Math.floor(gaussianWidth / width * (j - middlePoint) / this[kPointsPerUnit] + gaussianFactor * gaussianWidth / 2); | ||
if (gaussianIndex >= 0 && gaussianIndex < gaussian.length) { | ||
this[kSpectrum].y[index] += gaussian[gaussianIndex] * intensity; | ||
} | ||
} | ||
if (!widthLeft) widthLeft = width; | ||
if (!widthRight) widthRight = width; | ||
const firstValue = value - (widthLeft / 2 * gaussianFactor); | ||
const lastValue = value + (widthRight / 2 * gaussianFactor); | ||
const firstPoint = Math.floor(firstValue * this.pointsPerUnit); | ||
const lastPoint = Math.ceil(lastValue * this.pointsPerUnit); | ||
const middlePoint = value * this.pointsPerUnit; | ||
// we calculate the left part of the gaussian | ||
for (let j = firstPoint; j < middlePoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
if (index >= 0 && index < this.size) { | ||
let gaussianIndex = Math.floor(gaussianWidth / widthLeft * (j - middlePoint) / this.pointsPerUnit + gaussianFactor * gaussianWidth / 2); | ||
if (gaussianIndex >= 0 && gaussianIndex < gaussian.length) { | ||
this.data.y[index] += gaussian[gaussianIndex] * intensity; | ||
} | ||
} | ||
} | ||
return this; | ||
// we calculate the right part of the gaussian | ||
for (let j = middlePoint; j <= lastPoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
if (index >= 0 && index < this.size) { | ||
let gaussianIndex = Math.floor(gaussianWidth / widthRight * (j - middlePoint) / this.pointsPerUnit + gaussianFactor * gaussianWidth / 2); | ||
if (gaussianIndex >= 0 && gaussianIndex < gaussian.length) { | ||
this.data.y[index] += gaussian[gaussianIndex] * intensity; | ||
} | ||
} | ||
} | ||
/** | ||
return this; | ||
} | ||
addBaseline(baselineFct) { | ||
addBaseline(this.data, baselineFct); | ||
} | ||
addNoise(percent, options) { | ||
addNoise(this.data, percent, options); | ||
} | ||
/** | ||
* Get the generated spectrum. | ||
@@ -121,52 +175,49 @@ * @param {boolean} [copy=true] - If true, returns a copy of the spectrum. | ||
*/ | ||
getSpectrum(copy = true) { | ||
if (copy) { | ||
return { | ||
x: this[kSpectrum].x.slice(), | ||
y: this[kSpectrum].y.slice() | ||
}; | ||
} else { | ||
return this[kSpectrum]; | ||
} | ||
getSpectrum(copy = true) { | ||
if (copy) { | ||
return { | ||
x: this.data.x.slice(), | ||
y: this.data.y.slice() | ||
}; | ||
} else { | ||
return this.data; | ||
} | ||
} | ||
/** | ||
/** | ||
* Resets the generator with an empty spectrum. | ||
* @return {this} | ||
*/ | ||
reset() { | ||
let finalSize = (this[kEnd] - this[kStart]) * this[kPointsPerUnit] + 1; | ||
if (finalSize > this[kMaxSize]) { | ||
throw new Error('Generated array has size ' + finalSize + ' larger than maxSize: ' + this[kMaxSize]); | ||
} | ||
reset() { | ||
if (this.size > this.maxSize) { | ||
throw new Error(`Generated array has size ${this.size} larger than maxSize: ${this.maxSize}`); | ||
} | ||
const spectrum = this[kSpectrum] = { | ||
x: [], | ||
y: [] | ||
}; | ||
const spectrum = this.data = { | ||
x: [], | ||
y: new Array(this.size).fill(0) | ||
}; | ||
const interval = 1 / this[kPointsPerUnit]; | ||
const js = []; | ||
for (let j = 0; j < this[kPointsPerUnit]; j++) { | ||
js.push(j * interval); | ||
} | ||
const interval = 1 / this.pointsPerUnit; | ||
const js = []; | ||
for (let j = 0; j < this.pointsPerUnit; j++) { | ||
js.push(j * interval); | ||
} | ||
for (let i = this[kStart]; i < this[kEnd]; i++) { | ||
for (let j = 0; j < this[kPointsPerUnit]; j++) { | ||
spectrum.x.push(i + js[j]); | ||
spectrum.y.push(0); | ||
} | ||
} | ||
for (let i = this.start; i < this.end; i++) { | ||
for (let j = 0; j < this.pointsPerUnit; j++) { | ||
spectrum.x.push(i + js[j]); | ||
} | ||
} | ||
spectrum.x.push(this[kEnd]); | ||
spectrum.y.push(0); | ||
spectrum.x.push(this.end); | ||
return this; | ||
} | ||
return this; | ||
} | ||
} | ||
function assertInteger(value, name) { | ||
if (!Number.isInteger(value)) { | ||
throw new TypeError(`${name} option must be an integer`); | ||
} | ||
if (!Number.isInteger(value)) { | ||
throw new TypeError(`${name} option must be an integer`); | ||
} | ||
} | ||
@@ -180,6 +231,8 @@ | ||
*/ | ||
export function generateSpectrum(peaks, options) { | ||
const generator = new SpectrumGenerator(options); | ||
generator.addPeaks(peaks); | ||
return generator.getSpectrum(); | ||
export function generateSpectrum(peaks, options = {}) { | ||
const generator = new SpectrumGenerator(options); | ||
generator.addPeaks(peaks); | ||
if (options.baseline) generator.addBaseline(options.baseline); | ||
if (options.noise) generator.addNoise(options.noise.percent, options.noise); | ||
return generator.getSpectrum(); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36764
16
723
3
11
+ Addeddebug@^3.1.0
+ Addedrandom@^2.0.12
+ Addedseedrandom@^2.4.3
+ Addedbabel-runtime@6.26.0(transitive)
+ Addedcore-js@2.6.12(transitive)
+ Addeddebug@3.2.7(transitive)
+ Addedms@2.1.3(transitive)
+ Addedow@0.4.0(transitive)
+ Addedow-lite@0.0.2(transitive)
+ Addedrandom@2.2.0(transitive)
+ Addedregenerator-runtime@0.11.1(transitive)
+ Addedseedrandom@2.4.43.0.5(transitive)