Comparing version 1.0.3 to 2.0.0
128
lib/index.js
'use strict'; | ||
var mlSpectraProcessing = require('ml-spectra-processing'); | ||
var cuthillMckee = require('cuthill-mckee'); | ||
@@ -358,5 +359,37 @@ | ||
function getControlPoints(x, y, options = {}) { | ||
const { length } = x; | ||
let { controlPoints = Int8Array.from({ length }).fill(0) } = options; | ||
const { zones = [], weights = Float64Array.from({ length }).fill(1) } = | ||
options; | ||
if (x.length !== y.length) { | ||
throw new RangeError('Y should match the length with X'); | ||
} else if (controlPoints.length !== x.length) { | ||
throw new RangeError('controlPoints should match the length with X'); | ||
} else if (weights.length !== x.length) { | ||
throw new RangeError('weights should match the length with X'); | ||
} | ||
zones.forEach((range) => { | ||
let indexFrom = getCloseIndex(x, range.from); | ||
let indexTo = getCloseIndex(x, range.to); | ||
if (indexFrom > indexTo) [indexFrom, indexTo] = [indexTo, indexFrom]; | ||
for (let i = indexFrom; i < indexTo; i++) { | ||
controlPoints[i] = 1; | ||
} | ||
}); | ||
return { | ||
weights: | ||
'controlPoints' in options || zones.length > 0 | ||
? mlSpectraProcessing.xMultiply(weights, controlPoints) | ||
: weights, | ||
controlPoints, | ||
}; | ||
} | ||
/** | ||
* Fit the baseline drift by iteratively changing weights of sum square error between the fitted baseline and original signals, | ||
* for further information about the parameters you can get the [paper of airPLS](https://github.com/zmzhang/airPLS/blob/master/airPLS_manuscript.pdf) | ||
* for further information about the parameters you can get the [paper of airPLS](https://github.com/zmzhang/airPLS/blob/main/airPLS_manuscript.pdf) | ||
* @param {Array<number>} x - x axis data useful when control points or zones are submitted | ||
@@ -369,41 +402,25 @@ * @param {Array<number>} y - Original data | ||
* @param {number} [options.lambda = 100] - Factor of weights matrix in -> [I + lambda D'D]z = x | ||
* @param {Array<number>} [options.controlPoints = []] - Array of x axis values to force that baseline cross those points. | ||
* @param {Array<number>} [options.baseLineZones = []] - Array of x axis values (as from - to), to force that baseline cross those zones. | ||
* @param {Array<number>} [options.controlPoints = []] - Array of 0|1 to force the baseline cross those points. | ||
* @param {Array<number>} [options.zones = []] - Array of x axis values (as from - to), to force that baseline cross those zones. | ||
* @returns {{corrected: Array<number>, error: number, iteration: number, baseline: Array<number>}} | ||
*/ | ||
function airPLS(x, y, options = {}) { | ||
let { | ||
maxIterations = 100, | ||
lambda = 100, | ||
tolerance = 0.001, | ||
weights = new Array(y.length).fill(1), | ||
controlPoints = [], | ||
baseLineZones = [], | ||
} = options; | ||
const { weights, controlPoints } = getControlPoints(x, y, options); | ||
let { maxIterations = 100, lambda = 10, tolerance = 0.001 } = options; | ||
if (controlPoints.length > 0) { | ||
controlPoints.forEach((e, i, arr) => (arr[i] = getCloseIndex(x, e))); | ||
} | ||
if (baseLineZones.length > 0) { | ||
baseLineZones.forEach((range) => { | ||
let indexFrom = getCloseIndex(x, range.from); | ||
let indexTo = getCloseIndex(x, range.to); | ||
if (indexFrom > indexTo) [indexFrom, indexTo] = [indexTo, indexFrom]; | ||
for (let i = indexFrom; i < indexTo; i++) { | ||
controlPoints.push(i); | ||
} | ||
}); | ||
} | ||
let baseline, iteration; | ||
let nbPoints = y.length; | ||
let l = nbPoints - 1; | ||
let sumNegDifferences = Number.MAX_SAFE_INTEGER; | ||
let stopCriterion = tolerance * y.reduce((sum, e) => Math.abs(e) + sum, 0); | ||
const corrected = Float64Array.from(y); | ||
let stopCriterion = getStopCriterion(y, tolerance); | ||
const { length } = y; | ||
let { lowerTriangularNonZeros, permutationEncodedArray } = getDeltaMatrix( | ||
nbPoints, | ||
length, | ||
lambda, | ||
); | ||
let threshold = 1; | ||
const l = length - 1; | ||
let prevNegSum = Number.MAX_SAFE_INTEGER; | ||
for ( | ||
@@ -420,29 +437,36 @@ iteration = 0; | ||
let cho = prepare(leftHandSide, nbPoints, permutationEncodedArray); | ||
let cho = prepare(leftHandSide, length, permutationEncodedArray); | ||
baseline = cho(rightHandSide); | ||
sumNegDifferences = 0; | ||
sumNegDifferences = applyCorrection(y, baseline, corrected); | ||
if (iteration === 1) { | ||
const { positive } = mlSpectraProcessing.xNoiseSanPlot(corrected); | ||
threshold = positive; | ||
} else { | ||
const absChange = Math.abs(prevNegSum / sumNegDifferences); | ||
if (absChange < 1.01 && absChange > 0.99) { | ||
break; | ||
} | ||
} | ||
let difference = y.map(calculateError); | ||
let maxNegativeDiff = -1 * Number.MAX_SAFE_INTEGER; | ||
prevNegSum = sumNegDifferences + 0; | ||
for (let i = 1; i < l; i++) { | ||
let diff = difference[i]; | ||
if (diff >= 0) { | ||
const diff = corrected[i]; | ||
if (controlPoints[i] < 1 && Math.abs(diff) > threshold) { | ||
weights[i] = 0; | ||
} else { | ||
weights[i] = Math.exp((iteration * diff) / sumNegDifferences); | ||
if (maxNegativeDiff < diff) maxNegativeDiff = diff; | ||
const factor = diff > 0 ? -1 : 1; | ||
weights[i] = Math.exp( | ||
(factor * (iteration * diff)) / Math.abs(sumNegDifferences), | ||
); | ||
} | ||
} | ||
let value = Math.exp((iteration * maxNegativeDiff) / sumNegDifferences); | ||
weights[0] = value; | ||
weights[l] = value; | ||
controlPoints.forEach((i) => (weights[i] = value)); | ||
weights[0] = 1; | ||
weights[l] = 1; | ||
} | ||
return { | ||
corrected: y.map((e, i) => e - baseline[i]), | ||
corrected, | ||
baseline, | ||
@@ -453,9 +477,19 @@ iteration, | ||
function calculateError(e, i) { | ||
let diff = e - baseline[i]; | ||
if (diff < 0) sumNegDifferences += diff; | ||
return diff; | ||
function applyCorrection(y, baseline, corrected) { | ||
let sumNegDifferences = 0; | ||
for (let i = 0; i < y.length; i++) { | ||
let diff = y[i] - baseline[i]; | ||
if (diff < 0) sumNegDifferences += diff; | ||
corrected[i] = diff; | ||
} | ||
return sumNegDifferences; | ||
} | ||
} | ||
function getStopCriterion(y, tolerance) { | ||
let sum = mlSpectraProcessing.xAbsoluteSum(y); | ||
return tolerance * sum; | ||
} | ||
module.exports = airPLS; |
{ | ||
"name": "ml-airpls", | ||
"version": "1.0.3", | ||
"version": "2.0.0", | ||
"description": "Baseline correction using adaptive iteratively reweighted penalized least", | ||
@@ -14,4 +14,5 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"build": "cheminfo-build --entry src/index.js --root AirPLS", | ||
"compile": "rollup -c", | ||
"prepublishOnly": "npm run compile", | ||
"prepack": "npm run compile", | ||
"eslint": "eslint src", | ||
@@ -39,13 +40,15 @@ "eslint-fix": "npm run eslint -- --fix", | ||
"devDependencies": { | ||
"@babel/plugin-transform-modules-commonjs": "^7.16.8", | ||
"eslint": "^8.10.0", | ||
"eslint-config-cheminfo": "^8.0.2", | ||
"jest": "^29.5.0", | ||
"@babel/plugin-transform-modules-commonjs": "^7.24.1", | ||
"cheminfo-build": "^1.2.0", | ||
"eslint": "^8.57.0", | ||
"eslint-config-cheminfo": "^9.2.0", | ||
"jest": "^29.7.0", | ||
"jest-matcher-deep-close-to": "^3.0.2", | ||
"prettier": "^2.5.1", | ||
"rollup": "^3.22.0" | ||
"prettier": "^3.2.5", | ||
"rollup": "^4.17.2" | ||
}, | ||
"dependencies": { | ||
"cuthill-mckee": "^1.0.0" | ||
"cuthill-mckee": "^1.0.0", | ||
"ml-spectra-processing": "^14.5.0" | ||
} | ||
} |
@@ -9,3 +9,3 @@ # airpls | ||
It is an javascript implementation of [airpls](https://github.com/zmzhang/airPLS/blob/master/airPLS_manuscript.pdf) using cholesky decomposition and reverse Cuthill-Mckee method for reducing the bandwidth of sparse linear systems, obtaining a fast baseline fitter. | ||
It is an javascript implementation of [airpls](https://github.com/zmzhang/airPLS/blob/main/airPLS_manuscript.pdf) using cholesky decomposition and reverse Cuthill-Mckee method for reducing the bandwidth of sparse linear systems, obtaining a fast baseline fitter. | ||
@@ -12,0 +12,0 @@ ## Installation |
129
src/index.js
@@ -0,7 +1,41 @@ | ||
import { xMultiply, xNoiseSanPlot, xAbsoluteSum } from 'ml-spectra-processing'; | ||
import cholesky from './choleskySolver'; | ||
import { updateSystem, getDeltaMatrix, getCloseIndex } from './utils'; | ||
function getControlPoints(x, y, options = {}) { | ||
const { length } = x; | ||
let { controlPoints = Int8Array.from({ length }).fill(0) } = options; | ||
const { zones = [], weights = Float64Array.from({ length }).fill(1) } = | ||
options; | ||
if (x.length !== y.length) { | ||
throw new RangeError('Y should match the length with X'); | ||
} else if (controlPoints.length !== x.length) { | ||
throw new RangeError('controlPoints should match the length with X'); | ||
} else if (weights.length !== x.length) { | ||
throw new RangeError('weights should match the length with X'); | ||
} | ||
zones.forEach((range) => { | ||
let indexFrom = getCloseIndex(x, range.from); | ||
let indexTo = getCloseIndex(x, range.to); | ||
if (indexFrom > indexTo) [indexFrom, indexTo] = [indexTo, indexFrom]; | ||
for (let i = indexFrom; i < indexTo; i++) { | ||
controlPoints[i] = 1; | ||
} | ||
}); | ||
return { | ||
weights: | ||
'controlPoints' in options || zones.length > 0 | ||
? xMultiply(weights, controlPoints) | ||
: weights, | ||
controlPoints, | ||
}; | ||
} | ||
/** | ||
* Fit the baseline drift by iteratively changing weights of sum square error between the fitted baseline and original signals, | ||
* for further information about the parameters you can get the [paper of airPLS](https://github.com/zmzhang/airPLS/blob/master/airPLS_manuscript.pdf) | ||
* for further information about the parameters you can get the [paper of airPLS](https://github.com/zmzhang/airPLS/blob/main/airPLS_manuscript.pdf) | ||
* @param {Array<number>} x - x axis data useful when control points or zones are submitted | ||
@@ -14,41 +48,25 @@ * @param {Array<number>} y - Original data | ||
* @param {number} [options.lambda = 100] - Factor of weights matrix in -> [I + lambda D'D]z = x | ||
* @param {Array<number>} [options.controlPoints = []] - Array of x axis values to force that baseline cross those points. | ||
* @param {Array<number>} [options.baseLineZones = []] - Array of x axis values (as from - to), to force that baseline cross those zones. | ||
* @param {Array<number>} [options.controlPoints = []] - Array of 0|1 to force the baseline cross those points. | ||
* @param {Array<number>} [options.zones = []] - Array of x axis values (as from - to), to force that baseline cross those zones. | ||
* @returns {{corrected: Array<number>, error: number, iteration: number, baseline: Array<number>}} | ||
*/ | ||
export default function airPLS(x, y, options = {}) { | ||
let { | ||
maxIterations = 100, | ||
lambda = 100, | ||
tolerance = 0.001, | ||
weights = new Array(y.length).fill(1), | ||
controlPoints = [], | ||
baseLineZones = [], | ||
} = options; | ||
const { weights, controlPoints } = getControlPoints(x, y, options); | ||
let { maxIterations = 100, lambda = 10, tolerance = 0.001 } = options; | ||
if (controlPoints.length > 0) { | ||
controlPoints.forEach((e, i, arr) => (arr[i] = getCloseIndex(x, e))); | ||
} | ||
if (baseLineZones.length > 0) { | ||
baseLineZones.forEach((range) => { | ||
let indexFrom = getCloseIndex(x, range.from); | ||
let indexTo = getCloseIndex(x, range.to); | ||
if (indexFrom > indexTo) [indexFrom, indexTo] = [indexTo, indexFrom]; | ||
for (let i = indexFrom; i < indexTo; i++) { | ||
controlPoints.push(i); | ||
} | ||
}); | ||
} | ||
let baseline, iteration; | ||
let nbPoints = y.length; | ||
let l = nbPoints - 1; | ||
let sumNegDifferences = Number.MAX_SAFE_INTEGER; | ||
let stopCriterion = tolerance * y.reduce((sum, e) => Math.abs(e) + sum, 0); | ||
const corrected = Float64Array.from(y); | ||
let stopCriterion = getStopCriterion(y, tolerance); | ||
const { length } = y; | ||
let { lowerTriangularNonZeros, permutationEncodedArray } = getDeltaMatrix( | ||
nbPoints, | ||
length, | ||
lambda, | ||
); | ||
let threshold = 1; | ||
const l = length - 1; | ||
let prevNegSum = Number.MAX_SAFE_INTEGER; | ||
for ( | ||
@@ -65,29 +83,36 @@ iteration = 0; | ||
let cho = cholesky(leftHandSide, nbPoints, permutationEncodedArray); | ||
let cho = cholesky(leftHandSide, length, permutationEncodedArray); | ||
baseline = cho(rightHandSide); | ||
sumNegDifferences = 0; | ||
sumNegDifferences = applyCorrection(y, baseline, corrected); | ||
if (iteration === 1) { | ||
const { positive } = xNoiseSanPlot(corrected); | ||
threshold = positive; | ||
} else { | ||
const absChange = Math.abs(prevNegSum / sumNegDifferences); | ||
if (absChange < 1.01 && absChange > 0.99) { | ||
break; | ||
} | ||
} | ||
let difference = y.map(calculateError); | ||
let maxNegativeDiff = -1 * Number.MAX_SAFE_INTEGER; | ||
prevNegSum = sumNegDifferences + 0; | ||
for (let i = 1; i < l; i++) { | ||
let diff = difference[i]; | ||
if (diff >= 0) { | ||
const diff = corrected[i]; | ||
if (controlPoints[i] < 1 && Math.abs(diff) > threshold) { | ||
weights[i] = 0; | ||
} else { | ||
weights[i] = Math.exp((iteration * diff) / sumNegDifferences); | ||
if (maxNegativeDiff < diff) maxNegativeDiff = diff; | ||
const factor = diff > 0 ? -1 : 1; | ||
weights[i] = Math.exp( | ||
(factor * (iteration * diff)) / Math.abs(sumNegDifferences), | ||
); | ||
} | ||
} | ||
let value = Math.exp((iteration * maxNegativeDiff) / sumNegDifferences); | ||
weights[0] = value; | ||
weights[l] = value; | ||
controlPoints.forEach((i) => (weights[i] = value)); | ||
weights[0] = 1; | ||
weights[l] = 1; | ||
} | ||
return { | ||
corrected: y.map((e, i) => e - baseline[i]), | ||
corrected, | ||
baseline, | ||
@@ -98,7 +123,17 @@ iteration, | ||
function calculateError(e, i) { | ||
let diff = e - baseline[i]; | ||
if (diff < 0) sumNegDifferences += diff; | ||
return diff; | ||
function applyCorrection(y, baseline, corrected) { | ||
let sumNegDifferences = 0; | ||
for (let i = 0; i < y.length; i++) { | ||
let diff = y[i] - baseline[i]; | ||
if (diff < 0) sumNegDifferences += diff; | ||
corrected[i] = diff; | ||
} | ||
return sumNegDifferences; | ||
} | ||
} | ||
function getStopCriterion(y, tolerance) { | ||
let sum = xAbsoluteSum(y); | ||
return tolerance * sum; | ||
} |
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
34783
913
2
8
+ Addedbinary-search@1.3.6(transitive)
+ Addedcheminfo-types@1.8.1(transitive)
+ Addedd3-array@0.7.1(transitive)
+ Addedfft.js@4.0.4(transitive)
+ Addedis-any-array@2.0.1(transitive)
+ Addedml-array-max@1.2.4(transitive)
+ Addedml-array-min@1.2.3(transitive)
+ Addedml-array-rescale@1.3.7(transitive)
+ Addedml-matrix@6.12.0(transitive)
+ Addedml-spectra-processing@14.9.1(transitive)
+ Addedml-xsadd@3.0.1(transitive)
+ Addedspline-interpolator@1.0.0(transitive)