ml-spectra-fitting
Advanced tools
Comparing version 0.12.0 to 0.13.0
# Changelog | ||
## [0.13.0](https://www.github.com/mljs/spectra-fitting/compare/v0.12.0...v0.13.0) (2021-01-21) | ||
### Features | ||
* generate new version with checkInput function ([3782d1f](https://www.github.com/mljs/spectra-fitting/commit/3782d1f36f6672d74704109695e74946ae701d61)) | ||
* increment min max of width parameter ([7d7c324](https://www.github.com/mljs/spectra-fitting/commit/7d7c3244583b1cb8d444bb34ae6b5f324c7c3b35)) | ||
## [0.12.0](https://www.github.com/mljs/spectra-fitting/compare/v0.11.0...v0.12.0) (2021-01-13) | ||
@@ -4,0 +12,0 @@ |
428
lib/index.js
@@ -5,52 +5,13 @@ 'use strict'; | ||
var assignDeep = require('assign-deep'); | ||
var getMaxValue = require('ml-array-max'); | ||
var mlPeakShapeGenerator = require('ml-peak-shape-generator'); | ||
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 assignDeep__default = /*#__PURE__*/_interopDefaultLegacy(assignDeep); | ||
var getMaxValue__default = /*#__PURE__*/_interopDefaultLegacy(getMaxValue); | ||
var LM__default = /*#__PURE__*/_interopDefaultLegacy(LM); | ||
const LEVENBERG_MARQUARDT = 1; | ||
function selectMethod(optimizationOptions = {}) { | ||
let { kind, options } = optimizationOptions; | ||
kind = getKind(kind); | ||
switch (kind) { | ||
case LEVENBERG_MARQUARDT: | ||
return { | ||
algorithm: LM__default['default'], | ||
optimizationOptions: checkOptions(kind, options), | ||
}; | ||
default: | ||
throw new Error(`Unknown kind algorithm`); | ||
} | ||
} | ||
function checkOptions(kind, options = {}) { | ||
// eslint-disable-next-line default-case | ||
switch (kind) { | ||
case LEVENBERG_MARQUARDT: | ||
return Object.assign({}, lmOptions, options); | ||
} | ||
} | ||
function getKind(kind) { | ||
if (typeof kind !== 'string') return kind; | ||
switch (kind.toLowerCase().replace(/[^a-z]/g, '')) { | ||
case 'lm': | ||
case 'levenbergmarquardt': | ||
return LEVENBERG_MARQUARDT; | ||
default: | ||
throw new Error(`Unknown kind algorithm`); | ||
} | ||
} | ||
const lmOptions = { | ||
damping: 1.5, | ||
maxIterations: 100, | ||
errorTolerance: 1e-8, | ||
}; | ||
/** | ||
@@ -114,30 +75,3 @@ * This function calculates the spectrum as a sum of linear combination of gaussian and lorentzian functions. The pseudo voigt | ||
const STATE_INIT = 0; | ||
const STATE_MIN = 1; | ||
const STATE_MAX = 2; | ||
const STATE_GRADIENT_DIFFERENCE = 3; | ||
const X = 0; | ||
const Y = 1; | ||
const WIDTH = 2; | ||
const MU = 3; | ||
const keys = ['x', 'y', 'width', 'mu']; | ||
/** | ||
* Fits a set of points to the sum of a set of bell functions. | ||
* @param {object} data - An object containing the x and y data to be fitted. | ||
* @param {array} peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}]. | ||
* @param {object} [options = {}] | ||
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting. | ||
* @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported. | ||
* @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters. | ||
* @param {object} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt. | ||
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm. | ||
* @param {number} [options.optimization.options.timeout] - maximum time running before break in seconds. | ||
* @param {number} [options.optimization.options.damping=1.5] | ||
* @param {number} [options.optimization.options.maxIterations=100] | ||
* @param {number} [options.optimization.options.errorTolerance=1e-8] | ||
* @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 optimize(data, peaks, options = {}) { | ||
function checkInput(data, peaks, options) { | ||
let { | ||
@@ -150,19 +84,2 @@ shape = { kind: 'gaussian' }, | ||
let { | ||
minFactorWidth = 0.25, | ||
maxFactorWidth = 4, | ||
minFactorX = 2, | ||
maxFactorX = 2, | ||
minFactorY = 0, | ||
maxFactorY = 1.5, | ||
minMuValue = 0, | ||
maxMuValue = 1, | ||
xGradientDifference, | ||
yGradientDifference = 1e-3, | ||
widthGradientDifference, | ||
muGradientDifference = 0.01, | ||
} = optimization; | ||
peaks = JSON.parse(JSON.stringify(peaks)); | ||
if (typeof shape.kind !== 'string') { | ||
@@ -174,23 +91,79 @@ throw new Error('kind should be a string'); | ||
let x = data.x; | ||
let maxY = getMaxValue__default['default'](data.y); | ||
let y = new Array(x.length); | ||
for (let i = 0; i < x.length; i++) { | ||
y[i] = data.y[i] / maxY; | ||
} | ||
let nbParams; | ||
let paramsFunc; | ||
let defaultParameters; | ||
switch (kind) { | ||
case 'gaussian': | ||
nbParams = 3; | ||
paramsFunc = sumOfGaussians; | ||
defaultParameters = { | ||
x: { | ||
init: (peak) => peak.x, | ||
max: (peak) => peak.x + peak.width * 2, | ||
min: (peak) => peak.x - peak.width * 2, | ||
gradientDifference: (peak) => peak.width * 2e-3, | ||
}, | ||
y: { | ||
init: (peak) => peak.y, | ||
max: () => 1.5, | ||
min: () => 0, | ||
gradientDifference: () => 1e-3, | ||
}, | ||
width: { | ||
init: (peak) => peak.width, | ||
max: (peak) => peak.width * 4, | ||
min: (peak) => peak.width * 0.25, | ||
gradientDifference: (peak) => peak.width * 2e-3, | ||
}, | ||
}; | ||
break; | ||
case 'lorentzian': | ||
nbParams = 3; | ||
paramsFunc = sumOfLorentzians; | ||
defaultParameters = { | ||
x: { | ||
init: (peak) => peak.x, | ||
max: (peak) => peak.x + peak.width * 2, | ||
min: (peak) => peak.x - peak.width * 2, | ||
gradientDifference: (peak) => peak.width * 2e-3, | ||
}, | ||
y: { | ||
init: (peak) => peak.y, | ||
max: () => 1.5, | ||
min: () => 0, | ||
gradientDifference: () => 1e-3, | ||
}, | ||
width: { | ||
init: (peak) => peak.width, | ||
max: (peak) => peak.width * 4, | ||
min: (peak) => peak.width * 0.25, | ||
gradientDifference: (peak) => peak.width * 2e-3, | ||
}, | ||
}; | ||
break; | ||
case 'pseudovoigt': | ||
nbParams = 4; | ||
paramsFunc = sumOfGaussianLorentzians; | ||
defaultParameters = { | ||
x: { | ||
init: (peak) => peak.x, | ||
max: (peak) => peak.x + peak.width * 2, | ||
min: (peak) => peak.x - peak.width * 2, | ||
gradientDifference: (peak) => peak.width * 2e-3, | ||
}, | ||
y: { | ||
init: (peak) => peak.y, | ||
max: () => 1.5, | ||
min: () => 0, | ||
gradientDifference: () => 1e-3, | ||
}, | ||
width: { | ||
init: (peak) => peak.width, | ||
max: (peak) => peak.width * 4, | ||
min: (peak) => peak.width * 0.25, | ||
gradientDifference: (peak) => peak.width * 2e-3, | ||
}, | ||
mu: { | ||
init: (peak) => (peak.mu !== undefined ? peak.mu : 0.5), | ||
min: () => 0, | ||
max: () => 1, | ||
gradientDifference: () => 0.01, | ||
}, | ||
}; | ||
break; | ||
@@ -201,34 +174,159 @@ default: | ||
const getValueOptions = { | ||
let x = data.x; | ||
let maxY = getMaxValue__default['default'](data.y); | ||
let y = new Array(x.length); | ||
for (let i = 0; i < x.length; i++) { | ||
y[i] = data.y[i] / maxY; | ||
} | ||
for (let i = 0; i < peaks.length; i++) { | ||
peaks[i].y /= maxY; | ||
} | ||
let parameters = assignDeep__default['default']({}, optimization.parameters, defaultParameters); | ||
for (let key in parameters) { | ||
for (let par in parameters[key]) { | ||
if (!Array.isArray(parameters[key][par])) { | ||
parameters[key][par] = [parameters[key][par]]; | ||
} | ||
if ( | ||
parameters[key][par].length !== 1 && | ||
parameters[key][par].length !== peaks.length | ||
) { | ||
throw new Error(`The length of ${key}-${par} is not correct`); | ||
} | ||
for (let index = 0; index < parameters[key][par].length; index++) { | ||
if (typeof parameters[key][par][index] === 'number') { | ||
let value = parameters[key][par][index]; | ||
parameters[key][par][index] = () => value; | ||
} | ||
} | ||
} | ||
} | ||
optimization.parameters = parameters; | ||
return { | ||
y, | ||
x, | ||
maxY, | ||
minFactorX, | ||
maxFactorX, | ||
minFactorY, | ||
maxFactorY, | ||
minMuValue, | ||
maxMuValue, | ||
minFactorWidth, | ||
maxFactorWidth, | ||
xGradientDifference, | ||
yGradientDifference, | ||
widthGradientDifference, | ||
muGradientDifference, | ||
peaks, | ||
paramsFunc, | ||
optimization, | ||
}; | ||
} | ||
const LEVENBERG_MARQUARDT = 1; | ||
function selectMethod(optimizationOptions = {}) { | ||
let { kind, options } = optimizationOptions; | ||
kind = getKind(kind); | ||
switch (kind) { | ||
case LEVENBERG_MARQUARDT: | ||
return { | ||
algorithm: LM__default['default'], | ||
optimizationOptions: checkOptions(kind, options), | ||
}; | ||
default: | ||
throw new Error(`Unknown kind algorithm`); | ||
} | ||
} | ||
function checkOptions(kind, options = {}) { | ||
// eslint-disable-next-line default-case | ||
switch (kind) { | ||
case LEVENBERG_MARQUARDT: | ||
return Object.assign({}, lmOptions, options); | ||
} | ||
} | ||
function getKind(kind) { | ||
if (typeof kind !== 'string') return kind; | ||
switch (kind.toLowerCase().replace(/[^a-z]/g, '')) { | ||
case 'lm': | ||
case 'levenbergmarquardt': | ||
return LEVENBERG_MARQUARDT; | ||
default: | ||
throw new Error(`Unknown kind algorithm`); | ||
} | ||
} | ||
const lmOptions = { | ||
damping: 1.5, | ||
maxIterations: 100, | ||
errorTolerance: 1e-8, | ||
}; | ||
// const STATE_INIT = 0; | ||
// const STATE_MIN = 1; | ||
// const STATE_MAX = 2; | ||
// const STATE_GRADIENT_DIFFERENCE = 3; | ||
// const X = 0; | ||
// const Y = 1; | ||
// const WIDTH = 2; | ||
// const MU = 3; | ||
// const keys = ['x', 'y', 'width', 'mu']; | ||
/** | ||
* Fits a set of points to the sum of a set of bell functions. | ||
* @param {object} data - An object containing the x and y data to be fitted. | ||
* @param {array} peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}]. | ||
* @param {object} [options = {}] | ||
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting. | ||
* @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported. | ||
* @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters. | ||
* @param {object} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt. | ||
* @param {object} [options.optimization.parameters] - options of each parameter to be optimized e.g. For a gaussian shape | ||
* it could have x, y and with properties, each of which could contain init, min, max and gradientDifference, those options will define the guess, | ||
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number, | ||
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined. | ||
* @param {object} [options.optimization.parameters.x] - options for x parameter. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.init] - definition of the starting point of the parameter (the guess), | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.min] - definition of the lower limit of the parameter, | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.max] - definition of the upper limit of the parameter, | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.gradientDifference] - definition of the step size to approximate the jacobian matrix of the parameter, | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. | ||
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm. | ||
* @param {number} [options.optimization.options.timeout] - maximum time running before break in seconds. | ||
* @param {number} [options.optimization.options.damping=1.5] | ||
* @param {number} [options.optimization.options.maxIterations=100] | ||
* @param {number} [options.optimization.options.errorTolerance=1e-8] | ||
* @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 optimize(data, peakList, options = {}) { | ||
const { y, x, maxY, peaks, paramsFunc, optimization } = checkInput( | ||
data, | ||
peakList, | ||
options, | ||
); | ||
let parameters = optimization.parameters; | ||
let nbShapes = peaks.length; | ||
let pMin = new Float64Array(nbShapes * nbParams); | ||
let pMax = new Float64Array(nbShapes * nbParams); | ||
let pInit = new Float64Array(nbShapes * nbParams); | ||
let gradientDifference = new Float64Array(nbShapes * nbParams); | ||
let parameterKey = Object.keys(parameters); | ||
let nbParams = nbShapes * parameterKey.length; | ||
let pMin = new Float64Array(nbParams); | ||
let pMax = new Float64Array(nbParams); | ||
let pInit = new Float64Array(nbParams); | ||
let gradientDifference = new Float64Array(nbParams); | ||
for (let i = 0; i < nbShapes; i++) { | ||
let peak = peaks[i]; | ||
for (let s = 0; s < nbParams; s++) { | ||
pInit[i + s * nbShapes] = getValue(s, peak, STATE_INIT, getValueOptions); | ||
pMin[i + s * nbShapes] = getValue(s, peak, STATE_MIN, getValueOptions); | ||
pMax[i + s * nbShapes] = getValue(s, peak, STATE_MAX, getValueOptions); | ||
gradientDifference[i + s * nbShapes] = getValue( | ||
s, | ||
peak, | ||
STATE_GRADIENT_DIFFERENCE, | ||
getValueOptions, | ||
); | ||
for (let k = 0; k < parameterKey.length; k++) { | ||
let key = parameterKey[k]; | ||
let init = parameters[key].init; | ||
let min = parameters[key].min; | ||
let max = parameters[key].max; | ||
let gradientDifferenceValue = parameters[key].gradientDifference; | ||
pInit[i + k * nbShapes] = init[i % init.length](peak); | ||
pMin[i + k * nbShapes] = min[i % min.length](peak); | ||
pMax[i + k * nbShapes] = max[i % max.length](peak); | ||
gradientDifference[i + k * nbShapes] = gradientDifferenceValue[ | ||
i % gradientDifferenceValue.length | ||
](peak); | ||
} | ||
@@ -248,7 +346,7 @@ } | ||
let result = { error, iterations, peaks }; | ||
for (let i = 0; i < peaks.length; i++) { | ||
pFit.parameterValues[i + peaks.length] *= maxY; | ||
for (let s = 0; s < nbParams; s++) { | ||
for (let i = 0; i < nbShapes; i++) { | ||
pFit.parameterValues[i + nbShapes] *= maxY; | ||
for (let k = 0; k < parameterKey.length; k++) { | ||
// we modify the optimized parameters | ||
peaks[i][keys[s]] = pFit.parameterValues[i + s * peaks.length]; | ||
peaks[i][parameterKey[k]] = pFit.parameterValues[i + k * nbShapes]; | ||
} | ||
@@ -260,74 +358,2 @@ } | ||
function getValue(parameterIndex, peak, state, options) { | ||
let maxY = options.maxY; | ||
switch (state) { | ||
case STATE_INIT: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.x; | ||
case Y: | ||
return peak.y / maxY; | ||
case WIDTH: | ||
return peak.width; | ||
case MU: | ||
return peak.mu || 0.5; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
case STATE_GRADIENT_DIFFERENCE: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.xGradientDifference !== undefined | ||
? peak.xGradientDifference | ||
: options.xGradientDifference !== undefined | ||
? options.xGradientDifference | ||
: peak.width / 2e3; | ||
case Y: | ||
return peak.yGradientDifference !== undefined | ||
? peak.yGradientDifference | ||
: options.yGradientDifference; | ||
case WIDTH: | ||
return peak.widthGradientDifference !== undefined | ||
? peak.widthGradientDifference | ||
: options.widthGradientDifference !== undefined | ||
? options.widthGradientDifference | ||
: peak.width / 2e3; | ||
case MU: | ||
return peak.muGradientDifference !== undefined | ||
? peak.muGradientDifference | ||
: options.muGradientDifference; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
case STATE_MIN: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.x - peak.width * options.minFactorX; | ||
case Y: | ||
return (peak.y / maxY) * options.minFactorY; | ||
case WIDTH: | ||
return peak.width * options.minFactorWidth; | ||
case MU: | ||
return options.minMuValue; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
case STATE_MAX: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.x + peak.width * options.maxFactorX; | ||
case Y: | ||
return (peak.y / maxY) * options.maxFactorY; | ||
case WIDTH: | ||
return peak.width * options.maxFactorWidth; | ||
case MU: | ||
return options.maxMuValue; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
default: | ||
throw Error('the state is not supported'); | ||
} | ||
} | ||
exports.optimize = optimize; |
{ | ||
"name": "ml-spectra-fitting", | ||
"version": "0.12.0", | ||
"version": "0.13.0", | ||
"description": "Fit spectra using gaussian or lorentzian", | ||
@@ -64,2 +64,3 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"assign-deep": "^1.0.1", | ||
"ml-array-max": "^1.2.0", | ||
@@ -66,0 +67,0 @@ "ml-levenberg-marquardt": "3.1.0", |
227
src/index.js
@@ -1,19 +0,15 @@ | ||
import getMaxValue from 'ml-array-max'; | ||
import { checkInput } from './checkInput'; | ||
import { selectMethod } from './selectMethod'; | ||
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 STATE_GRADIENT_DIFFERENCE = 3; | ||
// const STATE_INIT = 0; | ||
// const STATE_MIN = 1; | ||
// const STATE_MAX = 2; | ||
// const STATE_GRADIENT_DIFFERENCE = 3; | ||
const X = 0; | ||
const Y = 1; | ||
const WIDTH = 2; | ||
const MU = 3; | ||
// const X = 0; | ||
// const Y = 1; | ||
// const WIDTH = 2; | ||
// const MU = 3; | ||
const keys = ['x', 'y', 'width', 'mu']; | ||
// const keys = ['x', 'y', 'width', 'mu']; | ||
/** | ||
@@ -28,2 +24,15 @@ * Fits a set of points to the sum of a set of bell functions. | ||
* @param {object} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt. | ||
* @param {object} [options.optimization.parameters] - options of each parameter to be optimized e.g. For a gaussian shape | ||
* it could have x, y and with properties, each of which could contain init, min, max and gradientDifference, those options will define the guess, | ||
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number, | ||
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined. | ||
* @param {object} [options.optimization.parameters.x] - options for x parameter. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.init] - definition of the starting point of the parameter (the guess), | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.min] - definition of the lower limit of the parameter, | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.max] - definition of the upper limit of the parameter, | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. | ||
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.gradientDifference] - definition of the step size to approximate the jacobian matrix of the parameter, | ||
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. | ||
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm. | ||
@@ -36,91 +45,33 @@ * @param {number} [options.optimization.options.timeout] - maximum time running before break in seconds. | ||
*/ | ||
export function optimize(data, peaks, options = {}) { | ||
let { | ||
shape = { kind: 'gaussian' }, | ||
optimization = { | ||
kind: 'lm', | ||
}, | ||
} = options; | ||
export function optimize(data, peakList, options = {}) { | ||
const { y, x, maxY, peaks, paramsFunc, optimization } = checkInput( | ||
data, | ||
peakList, | ||
options, | ||
); | ||
let { | ||
minFactorWidth = 0.25, | ||
maxFactorWidth = 4, | ||
minFactorX = 2, | ||
maxFactorX = 2, | ||
minFactorY = 0, | ||
maxFactorY = 1.5, | ||
minMuValue = 0, | ||
maxMuValue = 1, | ||
xGradientDifference, | ||
yGradientDifference = 1e-3, | ||
widthGradientDifference, | ||
muGradientDifference = 0.01, | ||
} = optimization; | ||
let parameters = optimization.parameters; | ||
peaks = JSON.parse(JSON.stringify(peaks)); | ||
let nbShapes = peaks.length; | ||
let parameterKey = Object.keys(parameters); | ||
let nbParams = nbShapes * parameterKey.length; | ||
let pMin = new Float64Array(nbParams); | ||
let pMax = new Float64Array(nbParams); | ||
let pInit = new Float64Array(nbParams); | ||
let gradientDifference = new Float64Array(nbParams); | ||
if (typeof shape.kind !== 'string') { | ||
throw new Error('kind should be a string'); | ||
} | ||
let kind = shape.kind.toLowerCase().replace(/[^a-z]/g, ''); | ||
let x = data.x; | ||
let maxY = getMaxValue(data.y); | ||
let y = new Array(x.length); | ||
for (let i = 0; i < x.length; i++) { | ||
y[i] = data.y[i] / maxY; | ||
} | ||
let nbParams; | ||
let paramsFunc; | ||
switch (kind) { | ||
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'); | ||
} | ||
const getValueOptions = { | ||
maxY, | ||
minFactorX, | ||
maxFactorX, | ||
minFactorY, | ||
maxFactorY, | ||
minMuValue, | ||
maxMuValue, | ||
minFactorWidth, | ||
maxFactorWidth, | ||
xGradientDifference, | ||
yGradientDifference, | ||
widthGradientDifference, | ||
muGradientDifference, | ||
}; | ||
let nbShapes = peaks.length; | ||
let pMin = new Float64Array(nbShapes * nbParams); | ||
let pMax = new Float64Array(nbShapes * nbParams); | ||
let pInit = new Float64Array(nbShapes * nbParams); | ||
let gradientDifference = new Float64Array(nbShapes * nbParams); | ||
for (let i = 0; i < nbShapes; i++) { | ||
let peak = peaks[i]; | ||
for (let s = 0; s < nbParams; s++) { | ||
pInit[i + s * nbShapes] = getValue(s, peak, STATE_INIT, getValueOptions); | ||
pMin[i + s * nbShapes] = getValue(s, peak, STATE_MIN, getValueOptions); | ||
pMax[i + s * nbShapes] = getValue(s, peak, STATE_MAX, getValueOptions); | ||
gradientDifference[i + s * nbShapes] = getValue( | ||
s, | ||
peak, | ||
STATE_GRADIENT_DIFFERENCE, | ||
getValueOptions, | ||
); | ||
for (let k = 0; k < parameterKey.length; k++) { | ||
let key = parameterKey[k]; | ||
let init = parameters[key].init; | ||
let min = parameters[key].min; | ||
let max = parameters[key].max; | ||
let gradientDifferenceValue = parameters[key].gradientDifference; | ||
pInit[i + k * nbShapes] = init[i % init.length](peak); | ||
pMin[i + k * nbShapes] = min[i % min.length](peak); | ||
pMax[i + k * nbShapes] = max[i % max.length](peak); | ||
gradientDifference[i + k * nbShapes] = gradientDifferenceValue[ | ||
i % gradientDifferenceValue.length | ||
](peak); | ||
} | ||
@@ -140,7 +91,7 @@ } | ||
let result = { error, iterations, peaks }; | ||
for (let i = 0; i < peaks.length; i++) { | ||
pFit.parameterValues[i + peaks.length] *= maxY; | ||
for (let s = 0; s < nbParams; s++) { | ||
for (let i = 0; i < nbShapes; i++) { | ||
pFit.parameterValues[i + nbShapes] *= maxY; | ||
for (let k = 0; k < parameterKey.length; k++) { | ||
// we modify the optimized parameters | ||
peaks[i][keys[s]] = pFit.parameterValues[i + s * peaks.length]; | ||
peaks[i][parameterKey[k]] = pFit.parameterValues[i + k * nbShapes]; | ||
} | ||
@@ -151,73 +102,1 @@ } | ||
} | ||
function getValue(parameterIndex, peak, state, options) { | ||
let maxY = options.maxY; | ||
switch (state) { | ||
case STATE_INIT: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.x; | ||
case Y: | ||
return peak.y / maxY; | ||
case WIDTH: | ||
return peak.width; | ||
case MU: | ||
return peak.mu || 0.5; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
case STATE_GRADIENT_DIFFERENCE: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.xGradientDifference !== undefined | ||
? peak.xGradientDifference | ||
: options.xGradientDifference !== undefined | ||
? options.xGradientDifference | ||
: peak.width / 2e3; | ||
case Y: | ||
return peak.yGradientDifference !== undefined | ||
? peak.yGradientDifference | ||
: options.yGradientDifference; | ||
case WIDTH: | ||
return peak.widthGradientDifference !== undefined | ||
? peak.widthGradientDifference | ||
: options.widthGradientDifference !== undefined | ||
? options.widthGradientDifference | ||
: peak.width / 2e3; | ||
case MU: | ||
return peak.muGradientDifference !== undefined | ||
? peak.muGradientDifference | ||
: options.muGradientDifference; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
case STATE_MIN: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.x - peak.width * options.minFactorX; | ||
case Y: | ||
return (peak.y / maxY) * options.minFactorY; | ||
case WIDTH: | ||
return peak.width * options.minFactorWidth; | ||
case MU: | ||
return options.minMuValue; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
case STATE_MAX: | ||
switch (parameterIndex) { | ||
case X: | ||
return peak.x + peak.width * options.maxFactorX; | ||
case Y: | ||
return (peak.y / maxY) * options.maxFactorY; | ||
case WIDTH: | ||
return peak.width * options.maxFactorWidth; | ||
case MU: | ||
return options.maxMuValue; | ||
default: | ||
throw new Error('The parameter is not supported'); | ||
} | ||
default: | ||
throw Error('the state is not supported'); | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
41996
13
737
4
1
+ Addedassign-deep@^1.0.1
+ Addedassign-deep@1.0.1(transitive)
+ Addedassign-symbols@2.0.2(transitive)