spectrum-generator
Advanced tools
Comparing version 3.2.2 to 4.0.0
@@ -0,1 +1,5 @@ | ||
# [4.0.0](https://github.com/cheminfo/spectrum-generator/compare/v3.2.2...v4.0.0) (2020-03-18) | ||
## [3.2.2](https://github.com/cheminfo/spectrum-generator/compare/v3.2.1...v3.2.2) (2020-03-13) | ||
@@ -2,0 +6,0 @@ |
146
lib/index.js
@@ -67,7 +67,6 @@ 'use strict'; | ||
{ | ||
start: 0, | ||
end: 1000, | ||
pointsPerUnit: 5, | ||
from: 0, | ||
to: 1000, | ||
nbPoints: 10001, | ||
peakWidthFct: () => 5, | ||
maxSize: 1e7, | ||
shape: { | ||
@@ -83,7 +82,7 @@ kind: 'gaussian', | ||
); | ||
this.start = options.start; | ||
this.end = options.end; | ||
this.pointsPerUnit = options.pointsPerUnit; | ||
this.from = options.from; | ||
this.to = options.to; | ||
this.nbPoints = options.nbPoints; | ||
this.interval = (this.to - this.from) / (this.nbPoints - 1); | ||
this.peakWidthFct = options.peakWidthFct; | ||
this.maxSize = options.maxSize; | ||
this.maxPeakHeight = Number.MIN_SAFE_INTEGER; | ||
@@ -94,11 +93,11 @@ this.shape = mlPeakShapeGenerator.getShape(options.shape.kind, options.shape.options); | ||
}); | ||
this.shapeFactor = this.shape.data.length / this.shape.fwhm; | ||
this.shapeFactor = (this.shape.data.length - 1) / this.shape.fwhm; | ||
this.shapeLength = this.shape.data.length; | ||
this.shapeHalfLength = Math.floor(this.shape.data.length / 2); | ||
assertNumber(this.from, 'from'); | ||
assertNumber(this.to, 'to'); | ||
assertInteger(this.nbPoints, 'nbPoints'); | ||
assertInteger(this.start, 'start'); | ||
assertInteger(this.end, 'end'); | ||
assertInteger(this.pointsPerUnit, 'pointsPerUnit'); | ||
assertInteger(this.maxSize, 'maxSize'); | ||
if (this.end <= this.start) { | ||
throw new RangeError('end option must be larger than start'); | ||
if (this.to <= this.from) { | ||
throw new RangeError('to option must be larger than from'); | ||
} | ||
@@ -113,6 +112,2 @@ | ||
get size() { | ||
return (this.end - this.start) * this.pointsPerUnit + 1; | ||
} | ||
addPeaks(peaks) { | ||
@@ -129,11 +124,26 @@ if (!Array.isArray(peaks)) { | ||
addPeak(peak, options = {}) { | ||
if (!Array.isArray(peak) || peak.length !== 2) { | ||
throw new Error('peak must be an array with two values'); | ||
if ( | ||
typeof peak !== 'object' || | ||
(peak.length !== 2 && (peak.x === undefined || peak.y === undefined)) | ||
) { | ||
throw new Error( | ||
'peak must be an array with two values or an object with {x,y}', | ||
); | ||
} | ||
let xPosition; | ||
let intensity; | ||
if (Array.isArray(peak)) { | ||
[xPosition, intensity] = peak; | ||
} else { | ||
xPosition = peak.x; | ||
intensity = peak.y; | ||
} | ||
const [value, intensity] = peak; | ||
if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity; | ||
let { width = this.peakWidthFct(value), widthLeft, widthRight } = options; | ||
let { | ||
width = this.peakWidthFct(xPosition), | ||
widthLeft, | ||
widthRight, | ||
} = options; | ||
@@ -143,40 +153,36 @@ if (!widthLeft) widthLeft = width; | ||
const firstValue = value - (widthLeft / 2) * this.shapeFactor; | ||
const lastValue = value + (widthRight / 2) * this.shapeFactor; | ||
const firstValue = xPosition - (widthLeft / 2) * this.shapeFactor; | ||
const lastValue = xPosition + (widthRight / 2) * this.shapeFactor; | ||
const firstPoint = Math.floor(firstValue * this.pointsPerUnit); | ||
const lastPoint = Math.ceil(lastValue * this.pointsPerUnit); | ||
const middlePoint = value * this.pointsPerUnit; | ||
const firstPoint = Math.max( | ||
0, | ||
Math.floor((firstValue - this.from) / this.interval), | ||
); | ||
const lastPoint = Math.min( | ||
this.nbPoints - 1, | ||
Math.ceil((lastValue - this.from) / this.interval), | ||
); | ||
const middlePoint = (xPosition - this.from) / this.interval; | ||
// PEAK SHAPE MAY BE ASYMETRC (widthLeft and widthRight) ! | ||
// PEAK SHAPE MAY BE ASYMMETRC (widthLeft and widthRight) ! | ||
// we calculate the left part of the shape | ||
for (let j = firstPoint; j < middlePoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
for (let index = firstPoint; index < middlePoint; index++) { | ||
let ratio = ((xPosition - this.data.x[index]) / widthLeft) * 2; | ||
let shapeIndex = Math.round( | ||
this.shapeHalfLength - (ratio * this.shape.fwhm) / 2, | ||
); | ||
if (index >= 0 && index < this.size) { | ||
let shapeIndex = Math.ceil( | ||
((this.shape.fwhm / widthLeft) * (j - middlePoint)) / | ||
this.pointsPerUnit + | ||
(this.shapeFactor * this.shape.fwhm - 1) / 2, | ||
); | ||
if (shapeIndex >= 0 && shapeIndex < this.shape.data.length) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
if (shapeIndex >= 0 && shapeIndex < this.shapeHalfLength) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
} | ||
// we calculate the right part of the gaussian | ||
for (let j = Math.ceil(middlePoint); j <= lastPoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
for (let index = Math.ceil(middlePoint); index <= lastPoint; index++) { | ||
let ratio = ((this.data.x[index] - xPosition) / widthRight) * 2; | ||
if (index >= 0 && index < this.size) { | ||
let shapeIndex = Math.floor( | ||
((this.shape.fwhm / widthRight) * (j - middlePoint)) / | ||
this.pointsPerUnit + | ||
(this.shapeFactor * this.shape.fwhm - 1) / 2, | ||
); | ||
if (shapeIndex >= 0 && shapeIndex < this.shape.data.length) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
let shapeIndex = Math.round( | ||
this.shapeHalfLength - (ratio * this.shape.fwhm) / 2, | ||
); | ||
if (shapeIndex >= 0 && shapeIndex <= this.shapeHalfLength) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
@@ -226,27 +232,11 @@ } | ||
reset() { | ||
if (this.size > this.maxSize) { | ||
throw new Error( | ||
`Generated array has size ${this.size} larger than maxSize: ${this.maxSize}`, | ||
); | ||
} | ||
const spectrum = (this.data = { | ||
x: [], | ||
y: new Array(this.size).fill(0), | ||
x: new Float64Array(this.nbPoints), | ||
y: new Float64Array(this.nbPoints), | ||
}); | ||
const interval = 1 / this.pointsPerUnit; | ||
const js = []; | ||
for (let j = 0; j < this.pointsPerUnit; j++) { | ||
js.push(j * interval); | ||
for (let i = 0; i < this.nbPoints; i++) { | ||
spectrum.x[i] = this.from + i * this.interval; | ||
} | ||
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.end); | ||
return this; | ||
@@ -262,2 +252,8 @@ } | ||
function assertNumber(value, name) { | ||
if (!Number.isFinite(value)) { | ||
throw new TypeError(`${name} option must be a number`); | ||
} | ||
} | ||
function generateSpectrum(peaks, options = {}) { | ||
@@ -264,0 +260,0 @@ const generator = new SpectrumGenerator(options); |
{ | ||
"name": "spectrum-generator", | ||
"version": "3.2.2", | ||
"version": "4.0.0", | ||
"description": "generate a spectrum from discrete peaks", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -10,6 +10,5 @@ # spectrum-generator | ||
In order to increase the speed a `shape` is first generated and then the peaks in the final | ||
spectrum are resulting from sampling the `shape`. A `shape` will therefore be generated with | ||
spectrum are resulting from sampling the `shape`. A `shape` will therefore be generated with | ||
much more points (typically fwhm:1000). | ||
## Installation | ||
@@ -35,3 +34,7 @@ | ||
]; | ||
const spectrum = generateSpectrum(peaks, { pointsPerUnit: 1 }); | ||
const spectrum = generateSpectrum(peaks, { | ||
from: 0, // default value: 0 | ||
to: 1000, // default value: 1000 | ||
nbPoints: 10001, // default value: 10001 | ||
}); | ||
``` | ||
@@ -50,4 +53,4 @@ | ||
]; | ||
const spectrum = generateSpectrum(peaks, { | ||
pointsPerUnit: 1000, | ||
const spectrum = generateSpectrum(peaks, { | ||
nbPoints: 1001, | ||
from: 0, | ||
@@ -60,8 +63,5 @@ to: 10, | ||
length: 10001, | ||
} | ||
} | ||
}, | ||
}, | ||
}); | ||
``` | ||
@@ -76,5 +76,6 @@ | ||
generator.addPeak([5, 20]); | ||
generator.addPeak({ x: 5, y: 20 }); // we may either add an array of 2 elements or an object with x,y values | ||
generator.addPeak([30, 56]); | ||
generator.addPeaks([ | ||
[40, 12], | ||
[40, 12], // it can also be an array of objects with x,y properties | ||
[10, 1], | ||
@@ -81,0 +82,0 @@ ]); |
@@ -6,3 +6,3 @@ export interface SpectrumGeneratorOptions { | ||
*/ | ||
start?: number; | ||
from?: number; | ||
@@ -13,3 +13,3 @@ /** | ||
*/ | ||
end?: number; | ||
to?: number; | ||
@@ -35,12 +35,6 @@ /** | ||
/** | ||
* Number of values between each unit of the x axis. | ||
* @default `5` | ||
* Number of points in the final spectrum. | ||
* @default `10001` | ||
*/ | ||
pointsPerUnit?: number; | ||
/** | ||
* Maximum array size. | ||
* @default `1e7` | ||
*/ | ||
maxSize?: number; | ||
nbPoints?: number; | ||
} | ||
@@ -101,2 +95,7 @@ | ||
export interface XYObject { | ||
x: number; | ||
y: number; | ||
} | ||
export class SpectrumGenerator { | ||
@@ -107,4 +106,5 @@ /** | ||
* 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] ); | ||
* const sg = new SG({from: 0, to: 100, nbPoints: 1001, peakWidthFct: (x) => 1 + 3 * x / 1000 }); | ||
* sg.addPeak([5, 50]); | ||
* sg.addPeak({x:10, y:50}); // either an array of an object with x,y properties | ||
* sg.addPeak([20, 100], { width: 3 }); | ||
@@ -121,5 +121,5 @@ * sg.addPeak([35, 100], { widthLeft: 10, widthRight: 30 }); | ||
* const spectrum=SG.generateSpectrum([ [20,3], [30,2], [40,2] ], { | ||
* start: 0, | ||
* end: 100, | ||
* pointsPerUnit: 1, | ||
* from: 0, | ||
* to: 100, | ||
* nbPoints: 101, | ||
* noise: { | ||
@@ -141,3 +141,3 @@ * percent: 10, | ||
*/ | ||
addPeaks(peaks: number[][]): this; | ||
addPeaks(peaks: number[][] | XYObject[]): this; | ||
@@ -149,3 +149,3 @@ /** | ||
*/ | ||
addPeak(peak: number[], options?: PeakOptions): this; | ||
addPeak(peak: number[] | XYObject, options?: PeakOptions): this; | ||
@@ -156,3 +156,3 @@ /** | ||
*/ | ||
addBaseline(baslineFct: (y: number) => number): this; | ||
addBaseline(baselineFct: (y: number) => number): this; | ||
@@ -159,0 +159,0 @@ /** |
import { SpectrumGenerator } from '..'; | ||
const numberReg = /^\w+ option must be a number$/; | ||
const integerReg = /^\w+ option must be an integer$/; | ||
const endStartReg = /^end option must be larger than start$/; | ||
const endStartReg = /^to option must be larger than from$/; | ||
const peakWidthReg = /^peakWidthFct option must be a function$/; | ||
const addPeaksReg = /^peaks must be an array$/; | ||
const addPeakReg = /^peak must be an array with two values$/; | ||
const addPeakReg = /^peak must be an array with two values or an object with {x,y}$/; | ||
describe('errors', () => { | ||
it('wrong options', () => { | ||
expect(() => new SpectrumGenerator({ start: 0.5 })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ start: false })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ from: 'abc' })).toThrow(numberReg); | ||
expect(() => new SpectrumGenerator({ from: false })).toThrow(numberReg); | ||
expect(() => new SpectrumGenerator({ end: 0.5 })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ end: false })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ to: 'abc' })).toThrow(numberReg); | ||
expect(() => new SpectrumGenerator({ to: false })).toThrow(numberReg); | ||
expect(() => new SpectrumGenerator({ pointsPerUnit: 0.5 })).toThrow( | ||
expect(() => new SpectrumGenerator({ nbPoints: 0.5 })).toThrow(integerReg); | ||
expect(() => new SpectrumGenerator({ nbPoints: false })).toThrow( | ||
integerReg, | ||
); | ||
expect(() => new SpectrumGenerator({ pointsPerUnit: false })).toThrow( | ||
integerReg, | ||
); | ||
expect(() => new SpectrumGenerator({ start: 0, end: 0 })).toThrow( | ||
expect(() => new SpectrumGenerator({ from: 0, to: 0 })).toThrow( | ||
endStartReg, | ||
); | ||
expect(() => new SpectrumGenerator({ start: 0, end: -10 })).toThrow( | ||
expect(() => new SpectrumGenerator({ from: 0, to: -10 })).toThrow( | ||
endStartReg, | ||
@@ -29,0 +28,0 @@ ); |
@@ -11,4 +11,4 @@ /* eslint-disable jest/expect-expect */ | ||
assertSimple({ | ||
start: 0, | ||
end: 10, | ||
from: 0, | ||
to: 10, | ||
peak: 5, | ||
@@ -18,6 +18,6 @@ }); | ||
it('should work from positive start', () => { | ||
it('should work from positive from', () => { | ||
assertSimple({ | ||
start: 5, | ||
end: 15, | ||
from: 5, | ||
to: 15, | ||
peak: 10, | ||
@@ -27,6 +27,6 @@ }); | ||
it('should work from negative start', () => { | ||
it('should work from negative from', () => { | ||
assertSimple({ | ||
start: -15, | ||
end: -5, | ||
from: -15, | ||
to: -5, | ||
peak: -10, | ||
@@ -38,23 +38,12 @@ }); | ||
describe('generateSpectrum with one peak and small window', () => { | ||
it('should work from 11', () => { | ||
const spectrum = generateSpectrum([[12, 1]], { | ||
start: 11, | ||
end: 13, | ||
pointsPerUnit: 10, | ||
it('should work with shape 9/3, peak width 0.2', () => { | ||
const spectrum = generateSpectrum([[10, 1]], { | ||
from: 9, | ||
to: 11, | ||
nbPoints: 21, | ||
peakWidthFct: () => 0.1, | ||
}); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(12); | ||
expect(max.y).toBe(1); | ||
}); | ||
it('should work from 0 to 10 low res', () => { | ||
const spectrum = generateSpectrum([[5, 1]], { | ||
start: 0, | ||
end: 10, | ||
pointsPerUnit: 1, | ||
peakWidthFct: () => 0.1, | ||
shape: { | ||
kind: 'gaussian', | ||
options: { | ||
length: 9, | ||
fwhm: 3, | ||
@@ -64,83 +53,76 @@ }, | ||
}); | ||
checkSymmetry(spectrum); | ||
expect(spectrum.y[9]).toBeCloseTo(0.0625, 3); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(5); | ||
expect(max.x).toBe(10); | ||
expect(max.y).toBe(1); | ||
}); | ||
it('should work from 10 to 20 low res', () => { | ||
const spectrum = generateSpectrum([[15, 1]], { | ||
start: 10, | ||
end: 20, | ||
pointsPerUnit: 1, | ||
peakWidthFct: () => 2, | ||
it('should work with shape 17/4, peak width 0.2', () => { | ||
const spectrum = generateSpectrum([[10, 1]], { | ||
from: 9, | ||
to: 11, | ||
nbPoints: 21, | ||
peakWidthFct: () => 0.4, | ||
shape: { | ||
kind: 'gaussian', | ||
options: { | ||
fwhm: 3, | ||
length: 17, | ||
fwhm: 4, | ||
}, | ||
}, | ||
}); | ||
for (let i = 0; i <= Math.floor(spectrum.y.length / 2); i++) { | ||
expect(spectrum.y[i]).toStrictEqual( | ||
spectrum.y[spectrum.y.length - i - 1], | ||
); | ||
} | ||
expect(spectrum.y[8]).toBe(0.5); | ||
checkSymmetry(spectrum); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(15); | ||
expect(max.x).toBe(10); | ||
expect(max.y).toBe(1); | ||
}); | ||
it('should work from 10 to 20 high res width 2', () => { | ||
const spectrum = generateSpectrum([[15, 1]], { | ||
start: 10, | ||
end: 20, | ||
pointsPerUnit: 1, | ||
peakWidthFct: () => 2, | ||
it('should work from 11', () => { | ||
const spectrum = generateSpectrum([[10, 1]], { | ||
from: 9, | ||
to: 11, | ||
nbPoints: 21, | ||
peakWidthFct: () => 0.1, | ||
shape: { | ||
kind: 'gaussian', | ||
options: { | ||
fwhm: 1000, | ||
length: 10, | ||
fwhm: 3, | ||
}, | ||
}, | ||
}); | ||
for (let i = 0; i <= Math.floor(spectrum.y.length / 2); i++) { | ||
expect(spectrum.y[i]).toStrictEqual( | ||
spectrum.y[spectrum.y.length - i - 1], | ||
); | ||
} | ||
expect(spectrum.y[4] + spectrum.y[6]).toBeCloseTo(1, 2); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(15); | ||
expect(spectrum.y[9]).toBeCloseTo(0.15749, 4); | ||
expect(max.x).toBe(10); | ||
expect(max.y).toBe(1); | ||
}); | ||
it('should work from 10 to 20 high res width 4', () => { | ||
const spectrum = generateSpectrum([[15, 1]], { | ||
start: 10, | ||
end: 20, | ||
pointsPerUnit: 1, | ||
peakWidthFct: () => 4, | ||
it('should work from 0 to 10 low res', () => { | ||
const spectrum = generateSpectrum([[5, 1]], { | ||
from: 0, | ||
to: 10, | ||
nbPoints: 101, | ||
peakWidthFct: () => 0.2, | ||
shape: { | ||
kind: 'gaussian', | ||
options: { | ||
factor: 2, | ||
fwhm: 1000, | ||
fwhm: 4, | ||
length: 13, | ||
}, | ||
}, | ||
}); | ||
for (let i = 0; i <= Math.floor(spectrum.y.length / 2); i++) { | ||
expect(spectrum.y[i]).toStrictEqual( | ||
spectrum.y[spectrum.y.length - i - 1], | ||
); | ||
} | ||
expect(spectrum.y[3] + spectrum.y[7]).toBeCloseTo(1, 2); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(15); | ||
expect(spectrum.y[49]).toBe(0.5); | ||
expect(max.x).toBe(5); | ||
expect(max.y).toBe(1); | ||
}); | ||
it('should work from 10 to 20 high res width 4 and pointsPerUnit 10', () => { | ||
it('should work from 10 to 20 low res', () => { | ||
const spectrum = generateSpectrum([[15, 1]], { | ||
start: 10, | ||
end: 20, | ||
pointsPerUnit: 10, | ||
from: 10, | ||
to: 20, | ||
nbPoints: 101, | ||
peakWidthFct: () => 2, | ||
@@ -150,13 +132,9 @@ shape: { | ||
options: { | ||
factor: 2, | ||
fwhm: 1000, | ||
fwhm: 500, | ||
length: 1501, | ||
}, | ||
}, | ||
}); | ||
for (let i = 0; i <= Math.floor(spectrum.y.length / 2); i++) { | ||
expect(spectrum.y[i]).toStrictEqual( | ||
spectrum.y[spectrum.y.length - i - 1], | ||
); | ||
} | ||
expect(spectrum.y[40] + spectrum.y[60]).toBeCloseTo(1, 2); | ||
checkSymmetry(spectrum); | ||
expect(spectrum.y[40]).toBe(0.5); | ||
let max = XY.maxYPoint(spectrum); | ||
@@ -166,44 +144,41 @@ expect(max.x).toBe(15); | ||
}); | ||
}); | ||
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, | ||
peakWidthFct: () => 0.1, | ||
}), | ||
).toThrow( | ||
'Generated array has size 10000001 larger than maxSize: 10000000', | ||
); | ||
it('not integer from / to', () => { | ||
const spectrum = generateSpectrum([[2, 1]], { | ||
from: 1.5, | ||
to: 2.5, | ||
nbPoints: 11, | ||
peakWidthFct: () => 0.1, | ||
}); | ||
checkSymmetry(spectrum); | ||
expect(spectrum.y[5]).toBe(1); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(2); | ||
expect(max.y).toBe(1); | ||
}); | ||
it('should throw for simple array is maxSize=1', () => { | ||
expect(() => | ||
generateSpectrum([[1, 1]], { | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 1, | ||
maxSize: 1, | ||
peakWidthFct: () => 0.1, | ||
}), | ||
).toThrow('Generated array has size 3 larger than maxSize: 1'); | ||
it('not integer from / to not integer', () => { | ||
const spectrum = generateSpectrum([[2.5, 1]], { | ||
from: 1.7, | ||
to: 3.7, | ||
nbPoints: 11, | ||
peakWidthFct: () => 0.1, | ||
}); | ||
expect(spectrum.y[3]).toBe(spectrum.y[5]); | ||
let max = XY.maxYPoint(spectrum); | ||
expect(max.x).toBe(2.5); | ||
expect(max.y).toBe(1); | ||
}); | ||
}); | ||
function assertSimple({ start, end, peak }) { | ||
function assertSimple({ from, to, peak }) { | ||
const spectrum = generateSpectrum([[peak, 1]], { | ||
start, | ||
end, | ||
pointsPerUnit: 1, | ||
from, | ||
to, | ||
nbPoints: 11, | ||
peakWidthFct: simplepeakWidthFct, | ||
}); | ||
assertSize(spectrum, end - start + 1); | ||
assertInterval(spectrum, start); | ||
assertSize(spectrum, to - from + 1); | ||
assertInterval(spectrum, from); | ||
} | ||
@@ -216,4 +191,4 @@ | ||
function assertInterval(spectrum, start) { | ||
let expected = start; | ||
function assertInterval(spectrum, from) { | ||
let expected = from; | ||
for (const value of spectrum.x) { | ||
@@ -224,1 +199,7 @@ expect(value).toBe(expected); | ||
} | ||
function checkSymmetry(spectrum) { | ||
for (let i = 0; i <= Math.floor(spectrum.y.length / 2); i++) { | ||
expect(spectrum.y[i]).toStrictEqual(spectrum.y[spectrum.y.length - i - 1]); | ||
} | ||
} |
@@ -7,5 +7,5 @@ /* eslint-disable jest/expect-expect */ | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5, | ||
from: 0, | ||
to: 2, | ||
nbPoints: 11, | ||
}); | ||
@@ -16,10 +16,10 @@ | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 0, 1); | ||
expect(spectrum.y[0]).toBe(1); | ||
}); | ||
it('end half peak', () => { | ||
it('to half peak', () => { | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5, | ||
from: 0, | ||
to: 2, | ||
nbPoints: 11, | ||
}); | ||
@@ -30,3 +30,3 @@ | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 2 * 5, 1); | ||
expect(spectrum.y[2 * 5]).toBe(1); | ||
}); | ||
@@ -36,5 +36,5 @@ | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 5, | ||
from: 0, | ||
to: 2, | ||
nbPoints: 11, | ||
}); | ||
@@ -45,3 +45,3 @@ | ||
const spectrum = generator.getSpectrum(); | ||
expectValue(spectrum, 1 * 5, 1); | ||
expect(spectrum.y[1 * 5]).toBe(1); | ||
}); | ||
@@ -51,7 +51,7 @@ | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 100, | ||
pointsPerUnit: 2, | ||
from: 0, | ||
to: 100, | ||
nbPoints: 201, | ||
}); | ||
generator.addPeak([35, 100], { widthLeft: 10, widthRight: 30 }); | ||
generator.addPeak([50, 100], { widthLeft: 10, widthRight: 30 }); | ||
const spectrum = generator.getSpectrum(); | ||
@@ -63,5 +63,5 @@ expect(spectrum).toMatchSnapshot(); | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 2, | ||
pointsPerUnit: 10, | ||
from: 0, | ||
to: 2, | ||
nbPoints: 21, | ||
peakWidthFct: (x) => 1 + (3 * x) / 1000, | ||
@@ -75,3 +75,3 @@ }); | ||
expect(spectrum.y[1.5 * 10]).toBeCloseTo(0.5, 2); | ||
expectValue(spectrum, 1 * 10, 1); | ||
expect(spectrum.y[1 * 10]).toBe(1); | ||
}); | ||
@@ -81,5 +81,5 @@ | ||
const generator = new SpectrumGenerator({ | ||
start: 0, | ||
end: 5, | ||
pointsPerUnit: 5, | ||
from: 0, | ||
to: 5, | ||
nbPoints: 26, | ||
}); | ||
@@ -112,14 +112,31 @@ | ||
expectValue(spectrum, 0, 1); | ||
expectValue(spectrum, 50 * 5, 12); | ||
expectValue(spectrum, 100 * 5, 10); | ||
expectValue(spectrum, 14 * 5, 2); | ||
expect(spectrum.y[0]).toBe(1); | ||
expect(spectrum.y[50 * 10]).toBe(12); | ||
expect(spectrum.y[100 * 10]).toBe(10); | ||
expect(spectrum.y[14 * 10]).toBe(2); | ||
}); | ||
it('full generation with {x,y}', () => { | ||
const generator = new SpectrumGenerator(); | ||
generator.addPeak({ x: 0, y: 1 }); | ||
generator.addPeak({ x: 50, y: 12 }); | ||
generator.addPeaks([ | ||
{ x: 100, y: 10 }, | ||
{ x: 14, y: 2 }, | ||
]); | ||
const spectrum = generator.getSpectrum(); | ||
expect(spectrum.y[0]).toBe(1); | ||
expect(spectrum.y[50 * 10]).toBe(12); | ||
expect(spectrum.y[100 * 10]).toBe(10); | ||
expect(spectrum.y[14 * 10]).toBe(2); | ||
}); | ||
it('full generation with threshold', () => { | ||
const generator = new SpectrumGenerator({ | ||
pointsPerUnit: 10000, | ||
start: -1000, | ||
end: 1000, | ||
maxSize: 1e8, | ||
from: -1000, | ||
to: 1000, | ||
nbPoints: 20000001, | ||
peakWidthFct: () => 0.001, | ||
@@ -160,5 +177,1 @@ }); | ||
}); | ||
function expectValue(spectrum, index, value) { | ||
expect(spectrum.y[index]).toBe(value); | ||
} |
146
src/index.js
@@ -12,7 +12,6 @@ import normed from 'ml-array-normed'; | ||
{ | ||
start: 0, | ||
end: 1000, | ||
pointsPerUnit: 5, | ||
from: 0, | ||
to: 1000, | ||
nbPoints: 10001, | ||
peakWidthFct: () => 5, | ||
maxSize: 1e7, | ||
shape: { | ||
@@ -28,7 +27,7 @@ kind: 'gaussian', | ||
); | ||
this.start = options.start; | ||
this.end = options.end; | ||
this.pointsPerUnit = options.pointsPerUnit; | ||
this.from = options.from; | ||
this.to = options.to; | ||
this.nbPoints = options.nbPoints; | ||
this.interval = (this.to - this.from) / (this.nbPoints - 1); | ||
this.peakWidthFct = options.peakWidthFct; | ||
this.maxSize = options.maxSize; | ||
this.maxPeakHeight = Number.MIN_SAFE_INTEGER; | ||
@@ -39,11 +38,11 @@ this.shape = getShape(options.shape.kind, options.shape.options); | ||
}); | ||
this.shapeFactor = this.shape.data.length / this.shape.fwhm; | ||
this.shapeFactor = (this.shape.data.length - 1) / this.shape.fwhm; | ||
this.shapeLength = this.shape.data.length; | ||
this.shapeHalfLength = Math.floor(this.shape.data.length / 2); | ||
assertNumber(this.from, 'from'); | ||
assertNumber(this.to, 'to'); | ||
assertInteger(this.nbPoints, 'nbPoints'); | ||
assertInteger(this.start, 'start'); | ||
assertInteger(this.end, 'end'); | ||
assertInteger(this.pointsPerUnit, 'pointsPerUnit'); | ||
assertInteger(this.maxSize, 'maxSize'); | ||
if (this.end <= this.start) { | ||
throw new RangeError('end option must be larger than start'); | ||
if (this.to <= this.from) { | ||
throw new RangeError('to option must be larger than from'); | ||
} | ||
@@ -58,6 +57,2 @@ | ||
get size() { | ||
return (this.end - this.start) * this.pointsPerUnit + 1; | ||
} | ||
addPeaks(peaks) { | ||
@@ -74,11 +69,26 @@ if (!Array.isArray(peaks)) { | ||
addPeak(peak, options = {}) { | ||
if (!Array.isArray(peak) || peak.length !== 2) { | ||
throw new Error('peak must be an array with two values'); | ||
if ( | ||
typeof peak !== 'object' || | ||
(peak.length !== 2 && (peak.x === undefined || peak.y === undefined)) | ||
) { | ||
throw new Error( | ||
'peak must be an array with two values or an object with {x,y}', | ||
); | ||
} | ||
let xPosition; | ||
let intensity; | ||
if (Array.isArray(peak)) { | ||
[xPosition, intensity] = peak; | ||
} else { | ||
xPosition = peak.x; | ||
intensity = peak.y; | ||
} | ||
const [value, intensity] = peak; | ||
if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity; | ||
let { width = this.peakWidthFct(value), widthLeft, widthRight } = options; | ||
let { | ||
width = this.peakWidthFct(xPosition), | ||
widthLeft, | ||
widthRight, | ||
} = options; | ||
@@ -88,40 +98,36 @@ if (!widthLeft) widthLeft = width; | ||
const firstValue = value - (widthLeft / 2) * this.shapeFactor; | ||
const lastValue = value + (widthRight / 2) * this.shapeFactor; | ||
const firstValue = xPosition - (widthLeft / 2) * this.shapeFactor; | ||
const lastValue = xPosition + (widthRight / 2) * this.shapeFactor; | ||
const firstPoint = Math.floor(firstValue * this.pointsPerUnit); | ||
const lastPoint = Math.ceil(lastValue * this.pointsPerUnit); | ||
const middlePoint = value * this.pointsPerUnit; | ||
const firstPoint = Math.max( | ||
0, | ||
Math.floor((firstValue - this.from) / this.interval), | ||
); | ||
const lastPoint = Math.min( | ||
this.nbPoints - 1, | ||
Math.ceil((lastValue - this.from) / this.interval), | ||
); | ||
const middlePoint = (xPosition - this.from) / this.interval; | ||
// PEAK SHAPE MAY BE ASYMETRC (widthLeft and widthRight) ! | ||
// PEAK SHAPE MAY BE ASYMMETRC (widthLeft and widthRight) ! | ||
// we calculate the left part of the shape | ||
for (let j = firstPoint; j < middlePoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
for (let index = firstPoint; index < middlePoint; index++) { | ||
let ratio = ((xPosition - this.data.x[index]) / widthLeft) * 2; | ||
let shapeIndex = Math.round( | ||
this.shapeHalfLength - (ratio * this.shape.fwhm) / 2, | ||
); | ||
if (index >= 0 && index < this.size) { | ||
let shapeIndex = Math.ceil( | ||
((this.shape.fwhm / widthLeft) * (j - middlePoint)) / | ||
this.pointsPerUnit + | ||
(this.shapeFactor * this.shape.fwhm - 1) / 2, | ||
); | ||
if (shapeIndex >= 0 && shapeIndex < this.shape.data.length) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
if (shapeIndex >= 0 && shapeIndex < this.shapeHalfLength) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
} | ||
// we calculate the right part of the gaussian | ||
for (let j = Math.ceil(middlePoint); j <= lastPoint; j++) { | ||
let index = j - this.start * this.pointsPerUnit; | ||
for (let index = Math.ceil(middlePoint); index <= lastPoint; index++) { | ||
let ratio = ((this.data.x[index] - xPosition) / widthRight) * 2; | ||
if (index >= 0 && index < this.size) { | ||
let shapeIndex = Math.floor( | ||
((this.shape.fwhm / widthRight) * (j - middlePoint)) / | ||
this.pointsPerUnit + | ||
(this.shapeFactor * this.shape.fwhm - 1) / 2, | ||
); | ||
if (shapeIndex >= 0 && shapeIndex < this.shape.data.length) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
let shapeIndex = Math.round( | ||
this.shapeHalfLength - (ratio * this.shape.fwhm) / 2, | ||
); | ||
if (shapeIndex >= 0 && shapeIndex <= this.shapeHalfLength) { | ||
this.data.y[index] += this.shape.data[shapeIndex] * intensity; | ||
} | ||
@@ -171,27 +177,11 @@ } | ||
reset() { | ||
if (this.size > this.maxSize) { | ||
throw new Error( | ||
`Generated array has size ${this.size} larger than maxSize: ${this.maxSize}`, | ||
); | ||
} | ||
const spectrum = (this.data = { | ||
x: [], | ||
y: new Array(this.size).fill(0), | ||
x: new Float64Array(this.nbPoints), | ||
y: new Float64Array(this.nbPoints), | ||
}); | ||
const interval = 1 / this.pointsPerUnit; | ||
const js = []; | ||
for (let j = 0; j < this.pointsPerUnit; j++) { | ||
js.push(j * interval); | ||
for (let i = 0; i < this.nbPoints; i++) { | ||
spectrum.x[i] = this.from + i * this.interval; | ||
} | ||
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.end); | ||
return this; | ||
@@ -207,2 +197,8 @@ } | ||
function assertNumber(value, name) { | ||
if (!Number.isFinite(value)) { | ||
throw new TypeError(`${name} option must be a number`); | ||
} | ||
} | ||
export function generateSpectrum(peaks, options = {}) { | ||
@@ -209,0 +205,0 @@ const generator = new SpectrumGenerator(options); |
Sorry, the diff of this file is not supported yet
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
47569
97
969