Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ml-spectra-fitting

Package Overview
Dependencies
Maintainers
4
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ml-spectra-fitting - npm Package Compare versions

Comparing version 0.2.1 to 0.3.0

CHANGELOG.md

573

lib/index.js

@@ -5,12 +5,15 @@ 'use strict';

var getMaxValue = require('ml-array-max');
var LM = require('ml-levenberg-marquardt');
var mlPeakShapeGenerator = require('ml-peak-shape-generator');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var getMaxValue__default = /*#__PURE__*/_interopDefaultLegacy(getMaxValue);
var LM__default = /*#__PURE__*/_interopDefaultLegacy(LM);
/**
* This function calculates the spectrum as a sum of lorentzian functions. The Lorentzian
* parameters are divided in 3 batches. 1st: centers; 2nd: heights; 3th: widths;
* @param t Ordinate values
* This function calculates the spectrum as a sum of linear combination of gaussian and lorentzian functions. The pseudo voigt
* parameters are divided in 4 batches. 1st: centers; 2nd: heights; 3th: widths; 4th: mu's ;
* @param t Ordinate value
* @param p Lorentzian parameters

@@ -23,26 +26,11 @@ * @returns {*}

let nL = p.length / 4;
let factorG1;
let factorG2;
let factorL;
let rows = t.length;
let p2;
let result = rows === undefined ? 0 : new Float64Array(rows).fill(0);
let result = 0;
for (let i = 0; i < nL; i++) {
let xG = p[i + nL * 3];
let xL = 1 - xG;
p2 = Math.pow(p[i + nL * 2] / 2, 2);
factorL = xL * p[i + nL] * p2;
factorG1 = p[i + nL * 2] * p[i + nL * 2] * 2;
factorG2 = xG * p[i + nL];
if (rows === undefined) {
result +=
factorG2 * Math.exp(-Math.pow(t - p[i], 2) / factorG1) +
factorL / (Math.pow(t - p[i], 2) + p2);
} else {
for (let j = 0; j < rows; j++) {
result[j] +=
factorG2 * Math.exp(-Math.pow(t[j] - p[i], 2) / factorG1) +
factorL / (Math.pow(t[j] - p[i], 2) + p2);
}
}
let func = mlPeakShapeGenerator.pseudovoigtFct({
x: p[i],
y: p[i + nL],
width: p[i + nL * 2],
mu: p[i + nL * 3],
});
result += func(t);
}

@@ -53,85 +41,21 @@ return result;

function optimizeGaussianLorentzianSum(xy, group, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
let nL = group.length;
let pInit = new Float64Array(nL * 4);
let pMin = new Float64Array(nL * 4);
let pMax = new Float64Array(nL * 4);
let dt = Math.abs(t[0] - t[1]);
for (let i = 0; i < nL; i++) {
pInit[i] = group[i].x;
pInit[i + nL] = 1;
pInit[i + 2 * nL] = group[i].width;
pInit[i + 3 * nL] = 0.5;
pMin[i] = group[i].x - dt;
pMin[i + nL] = 0;
pMin[i + 2 * nL] = group[i].width / 4;
pMin[i + 3 * nL] = 0;
pMax[i] = group[i].x + dt;
pMax[i + nL] = 1.5;
pMax[i + 2 * nL] = group[i].width * 4;
pMax[i + 3 * nL] = 1;
}
let data = {
x: t,
y: yData,
};
let result = new Array(nL);
let lmOptions = {
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
};
opts = Object.assign({}, lmOptions, opts);
let pFit = LM__default['default'](data, sumOfGaussianLorentzians, opts);
for (let i = 0; i < nL; i++) {
result[i] = {
parameters: [
pFit.parameterValues[i],
pFit.parameterValues[i + nL] * maxY,
pFit.parameterValues[i + 2 * nL],
pFit.parameterValues[i + 3 * nL],
],
error: pFit.parameterError,
};
}
return result;
}
/**
* This function calculates the spectrum as a sum of gaussian functions. The Gaussian
* parameters are divided in 3 batches. 1st: centers; 2nd: height; 3th: std's;
* parameters are divided in 3 batches. 1st: centers; 2nd: height; 3th: widths;
* @param t Ordinate values
* @param p Gaussian parameters
* @param c Constant parameters(Not used)
* @returns {*}
*/
function sumOfGaussians(p) {
return function (t) {
let nL = p.length / 3;
let factor;
let rows = t.length;
let result = rows === undefined ? 0 : new Float64Array(rows).fill(0);
let result = 0;
for (let i = 0; i < nL; i++) {
factor = Math.pow(p[i + nL * 2], 2) * 2;
if (rows === undefined) {
result += p[i + nL] * Math.exp(-Math.pow(t - p[i], 2) / factor);
} else {
for (let j = 0; j < rows; j++) {
result[j] += p[i + nL] * Math.exp(-Math.pow(t[j] - p[i], 2) / factor);
}
}
let func = mlPeakShapeGenerator.gaussianFct({
x: p[i],
y: p[i + nL],
width: p[i + nL * 2],
});
result += func(t);
}

@@ -143,173 +67,2 @@ return result;

/**
*
* @param xy A two column matrix containing the x and y data to be fitted
* @param group A set of initial lorentzian parameters to be optimized [center, heigth, half_width_at_half_height]
* @returns {Array} A set of final lorentzian parameters [center, heigth, hwhh*2]
*/
function optimizeGaussianSum(xy, group, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
let nL = group.length;
let pInit = new Float64Array(nL * 3);
let pMin = new Float64Array(nL * 3);
let pMax = new Float64Array(nL * 3);
let dt = Math.abs(t[0] - t[1]);
for (let i = 0; i < nL; i++) {
pInit[i] = group[i].x;
pInit[i + nL] = group[i].y / maxY;
pInit[i + 2 * nL] = group[i].width;
pMin[i] = group[i].x - dt;
pMin[i + nL] = 0;
pMin[i + 2 * nL] = group[i].width / 4;
pMax[i] = group[i].x + dt;
pMax[i + nL] = (group[i].y * 1.2) / maxY;
pMax[i + 2 * nL] = group[i].width * 4;
}
let data = {
x: t,
y: yData,
};
let result = new Array(nL);
let lmOptions = {
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
};
opts = Object.assign({}, lmOptions, opts);
let pFit = LM__default['default'](data, sumOfGaussians, opts);
for (let i = 0; i < nL; i++) {
result[i] = {
parameters: [
pFit.parameterValues[i],
pFit.parameterValues[i + nL] * maxY,
pFit.parameterValues[i + nL * 2],
],
error: pFit.parameterError,
};
}
return result;
}
/**
* Single 3 parameter gaussian function
* @param t Ordinate values
* @param p Gaussian parameters [mean, height, std]
* @param c Constant parameters(Not used)
* @returns {*}
*/
function singleGaussian(p) {
return function (t) {
let factor2 = (p[2] * p[2]) / 2;
let rows = t.length;
if (!rows) return p[1] * Math.exp((-(t - p[0]) * (t - p[0])) / factor2);
let result = new Float64Array(t.length);
for (let i = 0; i < t.length; i++) {
result[i] = p[1] * Math.exp((-(t[i] - p[0]) * (t[i] - p[0])) / factor2);
}
return result;
};
}
/**
* Fits a set of points to a gaussian bell. Returns the mean of the peak, the std and the height of the signal.
* @param data,[y]
* @returns {*[]}
*/
function optimizeSingleGaussian(xy, peak, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
let dt = Math.abs(t[0] - t[1]);
let pInit = new Float64Array([peak.x, 1, peak.width]);
let pMin = new Float64Array([peak.x - dt, 0, peak.width / 4]);
let pMax = new Float64Array([peak.x + dt, 1.25, peak.width * 4]);
let data = {
x: t,
y: yData,
};
let lmOptions = {
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
};
opts = Object.assign({}, lmOptions, opts);
let pFit = LM__default['default'](data, singleGaussian, opts);
return {
parameters: [
pFit.parameterValues[0],
pFit.parameterValues[1] * maxY,
pFit.parameterValues[2],
],
error: pFit.parameterError,
};
}
/*
peaks on group should sorted
*/
function optimizeGaussianTrain(xy, group, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
let currentIndex = 0;
let nbPoints = t.length;
let nextX;
let tI, yI;
let result = [];
let current;
for (let i = 0; i < group.length; i++) {
nextX = group[i].x - group[i].width * 1.5;
while (t[currentIndex++] < nextX && currentIndex < nbPoints);
nextX = group[i].x + group[i].width * 1.5;
tI = [];
yI = [];
while (t[currentIndex] <= nextX && currentIndex < nbPoints) {
tI.push(t[currentIndex]);
yI.push(yData[currentIndex] * maxY);
currentIndex++;
}
current = optimizeSingleGaussian([tI, yI], group[i], opts);
if (current) {
result.push({
x: current.parameters[0],
y: current.parameters[1],
width: current.parameters[2],
opt: true,
});
} else {
result.push({
x: group[i].x,
y: group[i].y,
width: group[i].width,
opt: false,
});
}
}
return result;
}
/**
* This function calculates the spectrum as a sum of lorentzian functions. The Lorentzian

@@ -325,16 +78,10 @@ * parameters are divided in 3 batches. 1st: centers; 2nd: heights; 3th: widths;

let nL = p.length / 3;
let factor;
let p2;
let rows = t.length;
let result = rows === undefined ? 0 : new Float64Array(rows).fill(0);
let result = 0;
for (let i = 0; i < nL; i++) {
p2 = Math.pow(p[i + nL * 2] / 2, 2);
factor = p[i + nL] * p2;
if (rows === undefined) {
result += factor / (Math.pow(t - p[i], 2) + p2);
} else {
for (let j = 0; j < rows; j++) {
result[j] += factor / (Math.pow(t[j] - p[i], 2) + p2);
}
}
let func = mlPeakShapeGenerator.lorentzianFct({
x: p[i],
y: p[i + nL],
width: p[i + nL * 2],
});
result += func(t);
}

@@ -345,63 +92,79 @@ return result;

const STATE_INIT = 0;
const STATE_MIN = 1;
const STATE_MAX = 2;
const keys = ['x', 'y', 'width', 'mu'];
/**
*
* @param xy A two column matrix containing the x and y data to be fitted
* @param group A set of initial lorentzian parameters to be optimized [center, heigth, half_width_at_half_height]
* @returns {Array} A set of final lorentzian parameters [center, heigth, hwhh*2]
* Fits a set of points to the sum of a set of bell functions.
* @param {Object} input - An object containing the x and y data to be fitted.
* @param {Array} peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {Object} [options = {}]
* @param {String} [options.kind = 'gaussian'] - kind of shape used to fitting, lorentzian, gaussian and pseudovoigt are supported.
* @param {Object} [options.lmOptions = {}] - options of ml-levenberg-marquardt optimization package.
* @returns {Object} - A object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
function optimizeLorentzianSum(xy, group, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
function optimize(data, peakList, options = {}) {
let { kind = 'gaussian', lmOptions = {} } = options;
let nL = group.length;
let pInit = new Float64Array(nL * 3);
let pMin = new Float64Array(nL * 3);
let pMax = new Float64Array(nL * 3);
let dt = Math.abs(t[0] - t[1]);
let maxY = getMaxValue__default['default'](data.y);
data.y.forEach((_, i, arr) => (arr[i] /= maxY));
for (let i = 0; i < nL; i++) {
pInit[i] = group[i].x;
pInit[i + nL] = 1;
pInit[i + 2 * nL] = group[i].width;
let nbParams;
let paramsFunc;
switch (kind.toLowerCase()) {
case 'gaussian':
nbParams = 3;
paramsFunc = sumOfGaussians;
break;
case 'lorentzian':
nbParams = 3;
paramsFunc = sumOfLorentzians;
break;
case 'pseudovoigt':
nbParams = 4;
paramsFunc = sumOfGaussianLorentzians;
break;
default:
throw new Error('kind of shape is not supported');
}
pMin[i] = group[i].x - dt;
pMin[i + nL] = 0;
pMin[i + 2 * nL] = group[i].width / 4;
let nL = peakList.length;
let pInit = new Float64Array(nL * nbParams);
let pMin = new Float64Array(nL * nbParams);
let pMax = new Float64Array(nL * nbParams);
let dt = Math.abs(data.x[0] - data.x[1]);
pMax[i] = group[i].x + dt;
pMax[i + nL] = 1.5;
pMax[i + 2 * nL] = group[i].width * 4;
for (let i = 0; i < nL; i++) {
let peak = peakList[i];
for (let s = 0; s < nbParams; s++) {
pInit[i + s * nL] = getValue(s, peak, STATE_INIT, dt);
pMin[i + s * nL] = getValue(s, peak, STATE_MIN, dt);
pMax[i + s * nL] = getValue(s, peak, STATE_MAX, dt);
}
}
let data = {
x: t,
y: yData,
};
lmOptions = Object.assign(
{
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
},
lmOptions,
);
let pFit = LM__default['default'](data, paramsFunc, lmOptions);
let result = new Array(nL);
let lmOptions = {
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
};
opts = Object.assign({}, lmOptions, opts);
let pFit = LM__default['default'](data, sumOfLorentzians, opts);
let { parameterError: error, iterations } = pFit;
let result = { error, iterations, peaks: new Array(nL) };
for (let i = 0; i < nL; i++) {
result[i] = {
parameters: [
pFit.parameterValues[i],
pFit.parameterValues[i + nL] * maxY,
pFit.parameterValues[i + nL * 2],
],
error: pFit.parameterError,
};
let peak = {};
pFit.parameterValues[i + nL] *= maxY;
for (let s = 0; s < nbParams; s++) {
peak[keys[s]] = pFit.parameterValues[i + s * nL];
}
result.peaks[i] = peak;
}

@@ -411,120 +174,30 @@ return result;

/**
* Single 4 parameter lorentzian function
* @param t Ordinate values
* @param p Lorentzian parameters
* @param c Constant parameters(Not used)
* @returns {*}
*/
function singleLorentzian(p) {
return function (t) {
let factor = p[1] * Math.pow(p[2] / 2, 2);
let rows = t.length;
if (!rows) return factor / (Math.pow(t - p[0], 2) + Math.pow(p[2] / 2, 2));
let result = new Float64Array(rows);
for (let i = 0; i < rows; i++) {
result[i] = factor / (Math.pow(t[i] - p[0], 2) + Math.pow(p[2] / 2, 2));
}
return result;
};
}
/**
* * Fits a set of points to a Lorentzian function. Returns the center of the peak, the width at half height, and the height of the signal.
* @param data,[y]
* @returns {*[]}
*/
function optimizeSingleLorentzian(xy, peak, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
let dt = Math.abs(t[0] - t[1]);
let pInit = new Float64Array([peak.x, 1, peak.width]);
let pMin = new Float64Array([peak.x - dt, 0.75, peak.width / 4]);
let pMax = new Float64Array([peak.x + dt, 1.25, peak.width * 4]);
let data = {
x: t,
y: yData,
};
let lmOptions = {
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
};
opts = Object.assign({}, lmOptions, opts);
let pFit = LM__default['default'](data, singleLorentzian, opts);
return {
parameters: [
pFit.parameterValues[0],
pFit.parameterValues[1] * maxY,
pFit.parameterValues[2],
],
error: pFit.parameterError,
};
}
/*
peaks on group should sorted
*/
function optimizeLorentzianTrain(xy, group, opts = {}) {
let t = xy[0];
let yData = xy[1];
let maxY = Math.max(...yData);
yData.forEach((x, i, arr) => (arr[i] /= maxY));
let currentIndex = 0;
let nbPoints = t.length;
let nextX;
let tI, yI;
let result = [];
let current;
for (let i = 0; i < group.length; i++) {
nextX = group[i].x - group[i].width * 1.5;
while (t[currentIndex++] < nextX && currentIndex < nbPoints);
nextX = group[i].x + group[i].width * 1.5;
tI = [];
yI = [];
while (t[currentIndex] <= nextX && currentIndex < nbPoints) {
tI.push(t[currentIndex]);
yI.push(yData[currentIndex] * maxY);
currentIndex++;
}
current = optimizeSingleLorentzian([tI, yI], group[i], opts);
if (current) {
result.push({
x: current.parameters[0],
y: current.parameters[1],
width: current.parameters[2],
opt: true,
});
} else {
result.push({
x: group[i].x,
y: group[i].y,
width: group[i].width,
opt: false,
});
}
function getValue(parameterIndex, peak, key, dt) {
let value;
switch (parameterIndex) {
case 0:
value =
key === STATE_INIT
? peak.x
: key === STATE_MIN
? peak.x - dt
: peak.x + dt;
break;
case 1:
value = key === STATE_INIT ? 1 : key === STATE_MIN ? 0 : 1.5;
break;
case 2:
value =
key === STATE_INIT
? peak.width
: key === STATE_MIN
? peak.width / 4
: peak.width * 4;
break;
default:
value = key === STATE_INIT ? 0.5 : key === STATE_MIN ? 0 : 1;
}
return result;
return value;
}
exports.optimizeGaussianLorentzianSum = optimizeGaussianLorentzianSum;
exports.optimizeGaussianSum = optimizeGaussianSum;
exports.optimizeGaussianTrain = optimizeGaussianTrain;
exports.optimizeLorentzianSum = optimizeLorentzianSum;
exports.optimizeLorentzianTrain = optimizeLorentzianTrain;
exports.optimizeSingleGaussian = optimizeSingleGaussian;
exports.optimizeSingleLorentzian = optimizeSingleLorentzian;
exports.singleGaussian = singleGaussian;
exports.singleLorentzian = singleLorentzian;
exports.sumOfGaussianLorentzians = sumOfGaussianLorentzians;
exports.sumOfGaussians = sumOfGaussians;
exports.sumOfLorentzians = sumOfLorentzians;
exports.optimize = optimize;
{
"name": "ml-spectra-fitting",
"version": "0.2.1",
"version": "0.3.0",
"description": "Fit spectra using guassian or lorentzian",

@@ -14,3 +14,4 @@ "main": "lib/index.js",

"eslint-fix": "npm run eslint -- --fix",
"prepublishOnly": "rollup -c",
"compile": "rollup -c",
"prepublishOnly": "npm run compile",
"test": "npm run test-coverage && npm run eslint",

@@ -36,2 +37,12 @@ "test-coverage": "jest --coverage",

"homepage": "https://github.com/mljs/spectra-fitting",
"jest": {
"testEnvironment": "node"
},
"prettier": {
"arrowParens": "always",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all"
},
"devDependencies": {

@@ -50,4 +61,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.10.4",

"dependencies": {
"ml-levenberg-marquardt": "^2.1.1"
"ml-array-max": "^1.2.0",
"ml-levenberg-marquardt": "^2.1.1",
"ml-peak-shape-generator": "^0.6.0",
"spectrum-generator": "^4.2.0"
}
}

@@ -0,14 +1,25 @@

[![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] [![npm download][download-image]][download-url]
# ml-spectra-fitting
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![npm download][download-image]][download-url]
Curve fitting method in javascript.
Bell optmization library. It contains:
This is spectra fitting package optimize the position (x), max intensity (y), full width at half maximum (width) and the percent of gaussian (mu). It supports three kind of shapes:
- `optimizeSingleLorentzian`: Function to fit a curve to a single 3 parameter Lorentzian function
- `optimizeLorentzianSum`: Function to fit a curve to a set of 3 parameters Lorentzian functions
- `optimizeSingleGaussian`: Function to fit a curve to a single 3 parameter Gaussian function
- `optimizeGaussianSum`: Function to fit a curve to a set of 3 parameters Gaussian functions
| Name | Equation |
| ------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| Gaussian | <img src="https://tex.cheminfo.org/?tex=y%20%5Ccdot%20exp%20%5Cleft%5B%5Cfrac%7B%5Cdelta%7D%7B2%20%5Csigma%5E2%7D%5Cright%5D"/> |
| Lorentzian | <img src="https://tex.cheminfo.org/?tex=y%5Ccdot%5Cfrac%7B%5Cgamma%7D%7B%5Cdelta%20%2B%20%5Cgamma%7D"/> |
| Pseudo Voigt | <img src="https://tex.cheminfo.org/?tex=y%20*%20%5Cleft%5B%5Cmu%20%5Ccdot%20exp%20%5Cleft%5B%5Cfrac%7B%5Cdelta%7D%7B2%20%5Csigma%5E2%7D%5Cright%5D%20%2B%20(1%20-%20%5Cmu)%20%5Ccdot%20%5Cfrac%7B%5Cgamma%7D%7B%5Cdelta%20%2B%20%5Cgamma%7D%20%5Cright%5D%0A"/> |
where
| <img src="https://tex.cheminfo.org/?tex=%5Cdelta%20%3D%20%5Cleft(t%20-%20x%5Cright)%5E2%0A"/> | <img src="https://tex.cheminfo.org/?tex=%5Csigma%20%3D%20%5Cfrac%7Bwidth%7D%7B2%5Csqrt%7B2%20%5Ccdot%20Log(2)%7D%7D"/> | <img src="https://tex.cheminfo.org/?tex=%5Cgamma%3D%5Cleft(width%5Cright)%5E2"/> |
| --------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------- |
It is a wrapper of [ml-levenberg-marquardt](https://github.com/mljs/levenberg-marquardt)
## [API Documentation](https://mljs.github.io/spectra-fitting/)
## Installation

@@ -18,2 +29,128 @@

## Example
```js
// import library
import { optimizeSum } from 'ml-spectra-fitting';
import { generateSpectrum } from 'spectrum-generator';
const peaks = [
{ x: 0.5, y: 0.2, width: 0.2 },
{ x: -0.5, y: 0.2, width: 0.3 },
];
const data = generateSpectrum(peaks, {from: -1, to: 1, nbPoints: 41});
//the approximate values to be optimized, It could come from a peak picking with ml-gsd
let peakList = [
{
x: -0.5,
y: 0.18,
width: 0.18,
},
{
x: 0.52,
y: 0.17,
width: 0.37,
},
];
// the function recive a peaklist with {x, y, width} as a guess
// and return a list of objects
let fittedParams = optimize(data, peakList);
console.log(fittedParams);
/**
{
error: 0.010502794375558983,
iterations: 15,
peaks: [
{
x: -0.49999760133593774,
y: 0.1999880261075537,
width: 0.3000369491704072
},
{
x: 0.5000084944744884,
y: 0.20004144804853427,
width: 0.1999731186595336
}
]
}
*/
```
For data with and combination of signals with shapes between gaussian and lorentzians, we could use the kind pseudovoigt to fit the data.
```js
import { optimize } from 'ml-spectra-fitting';
import { SpectrumGenerator } from 'spectrum-generator';
const generator = new SpectrumGenerator({
nbPoints: 101,
from: -1,
to: 1,
});
// by default the kind of shape is gaussian;
generator.addPeak({ x: 0.5, y: 0.2 }, { width: 0.2 });
generator.addPeak(
{ x: -0.5, y: 0.2 },
{
width: 0.1,
shape: {
kind: 'lorentzian',
options: {
fwhm: 1000,
length: 50001,
factor: 5
},
},
},
);
//points to fit {x, y};
let data = generator.getSpectrum();
console.log(JSON.stringify({x: Array.from(data.x), y: Array.from(data.y)}))
//the approximate values to be optimized, It could coming from a peak picking with ml-gsd
let peakList = [
{
x: -0.5,
y: 0.22,
width: 0.25,
},
{
x: 0.52,
y: 0.18,
width: 0.18,
},
];
// the function recive a peaklist with {x, y, width} as a guess
// and return a list of objects
let fittedParams = optimize(data, peakList, { kind: 'pseudovoigt' });
console.log(fittedParams);
/**
{
error: 0.12361588652854476,
iterations: 100,
peaks: [
{
x: -0.5000014532421942,
y: 0.19995307937326137,
width: 0.10007670374735196,
mu: 0.004731136777288483
},
{
x: 0.5001051783652894,
y: 0.19960010175400406,
width: 0.19935932346969124,
mu: 1
}
]
}
*/
```
## License

@@ -20,0 +157,0 @@

@@ -1,27 +0,115 @@

import { optimizeGaussianLorentzianSum } from './optimizeGaussianLorentzianSum';
import { optimizeGaussianSum } from './optimizeGaussianSum';
import { optimizeGaussianTrain } from './optimizeGaussianTrain';
import { optimizeLorentzianSum } from './optimizeLorentzianSum';
import { optimizeLorentzianTrain } from './optimizeLorentzianTrain';
import { optimizeSingleGaussian } from './optimizeSingleGaussian';
import { optimizeSingleLorentzian } from './optimizeSingleLorentzian';
import { singleGaussian } from './singleGaussian';
import { singleLorentzian } from './singleLorentzian';
import { sumOfGaussianLorentzians } from './sumOfGaussianLorentzians';
import { sumOfGaussians } from './sumOfGaussians';
import { sumOfLorentzians } from './sumOfLorentzians';
import getMaxValue from 'ml-array-max';
import LM from 'ml-levenberg-marquardt';
export {
optimizeGaussianLorentzianSum,
optimizeGaussianTrain,
optimizeGaussianSum,
optimizeLorentzianSum,
optimizeLorentzianTrain,
optimizeSingleGaussian,
optimizeSingleLorentzian,
singleGaussian,
singleLorentzian,
sumOfGaussianLorentzians,
sumOfGaussians,
sumOfLorentzians,
};
import { sumOfGaussianLorentzians } from './shapes/sumOfGaussianLorentzians';
import { sumOfGaussians } from './shapes/sumOfGaussians';
import { sumOfLorentzians } from './shapes/sumOfLorentzians';
const STATE_INIT = 0;
const STATE_MIN = 1;
const STATE_MAX = 2;
const keys = ['x', 'y', 'width', 'mu'];
/**
* Fits a set of points to the sum of a set of bell functions.
* @param {Object} input - An object containing the x and y data to be fitted.
* @param {Array} peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {Object} [options = {}]
* @param {String} [options.kind = 'gaussian'] - kind of shape used to fitting, lorentzian, gaussian and pseudovoigt are supported.
* @param {Object} [options.lmOptions = {}] - options of ml-levenberg-marquardt optimization package.
* @returns {Object} - A object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
export function optimize(data, peakList, options = {}) {
let { kind = 'gaussian', lmOptions = {} } = options;
let maxY = getMaxValue(data.y);
data.y.forEach((_, i, arr) => (arr[i] /= maxY));
let nbParams;
let paramsFunc;
switch (kind.toLowerCase()) {
case 'gaussian':
nbParams = 3;
paramsFunc = sumOfGaussians;
break;
case 'lorentzian':
nbParams = 3;
paramsFunc = sumOfLorentzians;
break;
case 'pseudovoigt':
nbParams = 4;
paramsFunc = sumOfGaussianLorentzians;
break;
default:
throw new Error('kind of shape is not supported');
}
let nL = peakList.length;
let pInit = new Float64Array(nL * nbParams);
let pMin = new Float64Array(nL * nbParams);
let pMax = new Float64Array(nL * nbParams);
let dt = Math.abs(data.x[0] - data.x[1]);
for (let i = 0; i < nL; i++) {
let peak = peakList[i];
for (let s = 0; s < nbParams; s++) {
pInit[i + s * nL] = getValue(s, peak, STATE_INIT, dt);
pMin[i + s * nL] = getValue(s, peak, STATE_MIN, dt);
pMax[i + s * nL] = getValue(s, peak, STATE_MAX, dt);
}
}
lmOptions = Object.assign(
{
damping: 1.5,
initialValues: pInit,
minValues: pMin,
maxValues: pMax,
gradientDifference: dt / 10000,
maxIterations: 100,
errorTolerance: 10e-5,
},
lmOptions,
);
let pFit = LM(data, paramsFunc, lmOptions);
let { parameterError: error, iterations } = pFit;
let result = { error, iterations, peaks: new Array(nL) };
for (let i = 0; i < nL; i++) {
let peak = {};
pFit.parameterValues[i + nL] *= maxY;
for (let s = 0; s < nbParams; s++) {
peak[keys[s]] = pFit.parameterValues[i + s * nL];
}
result.peaks[i] = peak;
}
return result;
}
function getValue(parameterIndex, peak, key, dt) {
let value;
switch (parameterIndex) {
case 0:
value =
key === STATE_INIT
? peak.x
: key === STATE_MIN
? peak.x - dt
: peak.x + dt;
break;
case 1:
value = key === STATE_INIT ? 1 : key === STATE_MIN ? 0 : 1.5;
break;
case 2:
value =
key === STATE_INIT
? peak.width
: key === STATE_MIN
? peak.width / 4
: peak.width * 4;
break;
default:
value = key === STATE_INIT ? 0.5 : key === STATE_MIN ? 0 : 1;
}
return value;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc