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

ml-gsd

Package Overview
Dependencies
Maintainers
10
Versions
77
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ml-gsd - npm Package Compare versions

Comparing version 6.1.2 to 6.2.0

src/post/__tests__/groupPeaks.test.js

17

CHANGELOG.md
# Changelog
## [6.2.0](https://www.github.com/mljs/global-spectral-deconvolution/compare/v6.1.2...v6.2.0) (2020-12-05)
### Features
* add factorLimits parameter for optimizePeaks ([1af47e1](https://www.github.com/mljs/global-spectral-deconvolution/commit/1af47e1a32cfef957cc43078ae389ecc4f2d691b))
* add failling test case ([a061013](https://www.github.com/mljs/global-spectral-deconvolution/commit/a06101315d97e70bc19119dad3692eff64848568))
* improve groupPeaks ([6ef7024](https://www.github.com/mljs/global-spectral-deconvolution/commit/6ef7024a1e34ce63c71fa0679946df74271d3c88))
* simplify optimizePeaks ([3abfe93](https://www.github.com/mljs/global-spectral-deconvolution/commit/3abfe93a4cc938d799acc495fea959d15db272a3))
* update ml-spectra-fitting ([97899d5](https://www.github.com/mljs/global-spectral-deconvolution/commit/97899d5f3453d6f0b0a4afa0c8102b07c5d69a32))
### Bug Fixes
* always ascending order in x dimension ([6ffc64f](https://www.github.com/mljs/global-spectral-deconvolution/commit/6ffc64f288220f4d004b7627c2fb44caa9b21bbf))
* update dependencies ([5ea4c7f](https://www.github.com/mljs/global-spectral-deconvolution/commit/5ea4c7fbb042c2145e760b97aab6c92788eeb117))
### [6.1.2](https://www.github.com/mljs/global-spectral-deconvolution/compare/v6.1.1...v6.1.2) (2020-11-18)

@@ -4,0 +21,0 @@

203

lib/index.js

@@ -8,2 +8,3 @@ 'use strict';

var mlSpectraFitting = require('ml-spectra-fitting');
var mlSpectraProcessing = require('ml-spectra-processing');

@@ -354,2 +355,33 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

/**
* Group peaks based on factor and add group property in peaks
* @param {array} peakList
* @param {number} factor
*/
function groupPeaks(peakList, factor = 1) {
if (peakList.length === 0) return [];
let peaks = peakList.sort((a, b) => a.x - b.x);
let previousPeak = { x: Number.NEGATIVE_INFINITY, width: 1 };
let currentGroup = [previousPeak];
let groups = [];
for (let peak of peaks) {
if (
(peak.x - previousPeak.x) / (peak.width + previousPeak.width) <=
factor / 2
) {
currentGroup.push(peak);
} else {
currentGroup = [peak];
groups.push(currentGroup);
}
peak.group = groups.length - 1;
previousPeak = peak;
}
return groups;
}
/**
* Optimize the position (x), max intensity (y), full width at half maximum (width)

@@ -361,3 +393,3 @@ * and the ratio of gaussian contribution (mu) if it's required. It supports three kind of shapes: gaussian, lorentzian and pseudovoigt

* @param {number} [options.factorWidth = 1] - times of width to group peaks.
* @param {object} [options.joinPeaks = true] - if true the peaks could be grouped if the separation between them are inside of a range of factorWidth * width
* @param {number} [options.factorLimits = 2] - times of width to use to optimize peaks
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.

@@ -373,3 +405,3 @@ * @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.

factorWidth = 1,
joinPeaks = true,
factorLimits = 2,
shape = {

@@ -383,152 +415,33 @@ kind: 'gaussian',

let { x, y } = data;
let lastIndex = [0];
let groups = groupPeaks(peakList, factorWidth, joinPeaks);
let result = [];
let sampling;
for (let i = 0; i < groups.length; i++) {
let peaks = groups[i].group;
if (peaks.length > 1) {
// Multiple peaks
sampling = sampleFunction(
groups[i].limits[0] - groups[i].limits[1],
groups[i].limits[0] + groups[i].limits[1],
x,
y,
lastIndex,
);
if (sampling.x.length > 5) {
let { peaks: optPeaks } = mlSpectraFitting.optimize(sampling, peaks, {
shape,
optimization,
});
for (let j = 0; j < optPeaks.length; j++) {
optPeaks[j].index = peaks.index;
optPeaks[j].group = i;
result.push(optPeaks[j]);
}
}
} else {
// Single peak
peaks = peaks[0];
sampling = sampleFunction(
peaks.x - factorWidth * peaks.width,
peaks.x + factorWidth * peaks.width,
x,
y,
lastIndex,
);
if (sampling.x.length > 5) {
let fitResult = mlSpectraFitting.optimize(sampling, [peaks], {
shape,
optimization,
});
let { peaks: optPeaks } = fitResult;
optPeaks[0].index = peaks.index;
optPeaks[0].group = i;
result.push(optPeaks[0]);
}
}
if (data.x[0] > data.x[1]) {
data.x.reverse();
data.y.reverse();
}
return result;
}
function sampleFunction(from, to, x, y, lastIndex) {
let nbPoints = x.length;
let sampleX = [];
let sampleY = [];
let direction = Math.sign(x[1] - x[0]); // Direction of the derivative
if (direction === -1) {
lastIndex[0] = x.length - 1;
}
let groups = groupPeaks(peakList, factorWidth);
let delta = Math.abs(to - from) / 2;
let mid = (from + to) / 2;
let stop = false;
let index = lastIndex[0];
while (!stop && index < nbPoints && index >= 0) {
if (Math.abs(x[index] - mid) <= delta) {
sampleX.push(x[index]);
sampleY.push(y[index]);
index += direction;
} else {
// It is outside the range.
if (Math.sign(mid - x[index]) === 1) {
// We'll reach the mid going in the current direction
index += direction;
} else {
// There is not more peaks in the current range
stop = true;
}
}
}
lastIndex[0] = index;
return { x: sampleX, y: sampleY };
}
let results = [];
for (const peaks of groups) {
const firstPeak = peaks[0];
const lastPeak = peaks[peaks.length - 1];
function groupPeaks(peakList, nL, joinPeaks) {
let group = [];
let groups = [];
let limits = [peakList[0].x, nL * peakList[0].width];
let upperLimit, lowerLimit;
// Merge forward
for (let i = 0; i < peakList.length; i++) {
// If the 2 things overlaps
if (
joinPeaks &&
Math.abs(peakList[i].x - limits[0]) < nL * peakList[i].width + limits[1]
) {
// Add the peak to the group
group.push(peakList[i]);
// Update the group limits
upperLimit = limits[0] + limits[1];
if (peakList[i].x + nL * peakList[i].width > upperLimit) {
upperLimit = peakList[i].x + nL * peakList[i].width;
}
lowerLimit = limits[0] - limits[1];
if (peakList[i].x - nL * peakList[i].width < lowerLimit) {
lowerLimit = peakList[i].x - nL * peakList[i].width;
}
limits = [
(upperLimit + lowerLimit) / 2,
Math.abs(upperLimit - lowerLimit) / 2,
];
const from = firstPeak.x - firstPeak.width * factorLimits;
const to = lastPeak.x + lastPeak.width * factorLimits;
const { fromIndex, toIndex } = mlSpectraProcessing.xGetFromToIndex(data.x, { from, to });
// Multiple peaks
const currentRange = {
x: data.x.slice(fromIndex, toIndex),
y: data.y.slice(fromIndex, toIndex),
};
if (currentRange.x.length > 5) {
let { peaks: optimizedPeaks } = mlSpectraFitting.optimize(currentRange, peaks, {
shape,
optimization,
});
results = results.concat(optimizedPeaks);
} else {
groups.push({ limits: limits, group: group });
group = [peakList[i]];
limits = [peakList[i].x, nL * peakList[i].width];
results = results.concat(peaks);
}
}
groups.push({ limits: limits, group: group });
// Merge backward
for (let i = groups.length - 2; i >= 0; i--) {
// The groups overlaps
if (
Math.abs(groups[i].limits[0] - groups[i + 1].limits[0]) <
(groups[i].limits[1] + groups[i + 1].limits[1]) / 2
) {
for (let j = 0; j < groups[i + 1].group.length; j++) {
groups[i].group.push(groups[i + 1].group[j]);
}
upperLimit = groups[i].limits[0] + groups[i].limits[1];
if (groups[i + 1].limits[0] + groups[i + 1].limits[1] > upperLimit) {
upperLimit = groups[i + 1].limits[0] + groups[i + 1].limits[1];
}
lowerLimit = groups[i].limits[0] - groups[i].limits[1];
if (groups[i + 1].limits[0] - groups[i + 1].limits[1] < lowerLimit) {
lowerLimit = groups[i + 1].limits[0] - groups[i + 1].limits[1];
}
groups[i].limits = [
(upperLimit + lowerLimit) / 2,
Math.abs(upperLimit - lowerLimit) / 2,
];
groups.splice(i + 1, 1);
}
}
return groups;
return results;
}

@@ -535,0 +448,0 @@

{
"name": "ml-gsd",
"version": "6.1.2",
"version": "6.2.0",
"description": "Global Spectra Deconvolution",

@@ -16,2 +16,3 @@ "main": "lib/index.js",

"build": "rollup -c && cheminfo-build --entry src/index.js --root GSD",
"example": "nodemon -w src -w examples/example.js -r esm examples/example.js",
"eslint": "eslint src --cache",

@@ -57,9 +58,10 @@ "eslint-fix": "npm run eslint -- --fix",

"@babel/plugin-transform-modules-commonjs": "^7.12.1",
"@types/jest": "^26.0.16",
"chemcalc": "^3.4.1",
"cheminfo-build": "^1.1.8",
"eslint": "^7.13.0",
"eslint": "^7.14.0",
"eslint-config-cheminfo": "^5.2.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.3",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-prettier": "^3.2.0",
"esm": "^3.2.25",

@@ -70,12 +72,14 @@ "jest": "^26.6.3",

"ml-stat": "^1.3.3",
"prettier": "^2.1.2",
"rollup": "^2.33.3",
"spectrum-generator": "^4.4.0",
"nodemon": "^2.0.6",
"prettier": "^2.2.1",
"rollup": "^2.34.1",
"spectrum-generator": "^4.4.2",
"xy-parser": "^3.0.0"
},
"dependencies": {
"ml-savitzky-golay-generalized": "2.0.2",
"ml-spectra-fitting": "^0.7.1",
"ml-peak-shape-generator": "^0.10.2"
"ml-peak-shape-generator": "^0.10.2",
"ml-savitzky-golay-generalized": "2.0.3",
"ml-spectra-fitting": "0.9.0",
"ml-spectra-processing": "^4.9.4"
}
}

@@ -6,12 +6,10 @@ import { gsd, optimizePeaks } from '..';

describe('Global spectra deconvolution with simulated spectra', () => {
// Test case obtained from Pag 443, Chap 8.
it('Should provide the right result ...', () => {
it('Overlapping peaks', () => {
const peaks = [
{ x: -0.1, y: 0.2, width: 0.3 },
{ x: 0.1, y: 0.2, width: 0.1 },
{ x: -0.1, y: 0.2, width: 0.3 },
];
const data = generateSpectrum(peaks, { from: -1, to: 1, nbPoints: 101 });
data.y.reverse();
data.x.reverse();
let peakList = gsd(data, {

@@ -22,31 +20,121 @@ minMaxRatio: 0,

heightFactor: 1,
shape: { kind: 'gaussian' },
});
let optPeaks = optimizePeaks(data, peakList);
let optimizedPeaks = optimizePeaks(data, peakList);
expect(optPeaks[0].x).toBeCloseTo(-0.1, 2);
expect(optPeaks[0].y).toBeCloseTo(0.2, 2);
expect(optPeaks[0].width).toBeCloseTo(0.3, 2);
expect(optPeaks[1].x).toBeCloseTo(0.1, 2);
expect(optPeaks[1].y).toBeCloseTo(0.2, 2);
expect(optPeaks[1].width).toBeCloseTo(0.1, 2);
expect(peakList[0].x).toBeCloseTo(-0.1, 2);
expect(peakList[0].y).toBeCloseTo(0.2, 2);
expect(peakList[0].width).toBeCloseTo(0.3, 2);
expect(peakList[1].x).toBeCloseTo(0.1, 2);
expect(peakList[1].y).toBeCloseTo(0.2, 2);
expect(peakList[1].width).toBeCloseTo(0.1, 2);
expect(optimizedPeaks[0].x).toBeCloseTo(-0.1, 2);
expect(optimizedPeaks[0].y).toBeCloseTo(0.2, 2);
expect(optimizedPeaks[0].width).toBeCloseTo(0.3, 2);
expect(optimizedPeaks[0].group).toBe(0);
expect(optimizedPeaks[1].x).toBeCloseTo(0.1, 2);
expect(optimizedPeaks[1].y).toBeCloseTo(0.2, 2);
expect(optimizedPeaks[1].width).toBeCloseTo(0.1, 2);
expect(optimizedPeaks[1].group).toBe(1);
});
it('Overlaped peaks', () => {
it('Check gaussian shapes with shape specification', () => {
const peaks = [
{ x: 0.1, y: 0.4, width: 0.0 },
{ x: 0.101, y: 0.5, width: 0.01 },
{ x: 0.15, y: 0.4, width: 0.01 },
{ x: 0.151, y: 0.3, width: 0.03 },
{ x: -0.5, y: 1, width: 0.2 },
{ x: 0.5, y: 1, width: 0.1 },
];
const data = generateSpectrum(peaks, { from: 0, to: 1, nbPoints: 101 });
const data = generateSpectrum(peaks, { from: -1, to: 1, nbPoints: 10001 });
let optPeaks = optimizePeaks(data, peaks, {
factorWidth: 1,
optimization: { kind: 'lm', options: { maxIterations: 300 } },
let peakList = gsd(data, {
minMaxRatio: 0,
realTopDetection: false,
smoothY: false,
heightFactor: 1,
shape: { kind: 'gaussian' }, // we specifiy we are expecting a gaussian shape
});
expect(optPeaks[0].x).toBeCloseTo(0.15, 2);
expect(optPeaks[0].y).toBeCloseTo(0.4, 2);
expect(optPeaks[0].width).toBeCloseTo(0.01, 2);
expect(peakList[0].x).toBeCloseTo(-0.5, 2);
expect(peakList[0].y).toBeCloseTo(1, 2);
expect(peakList[0].width).toBeCloseTo(0.2, 2); // inflection points in gaussian are higher tha FWHM
expect(peakList[1].x).toBeCloseTo(0.5, 2);
expect(peakList[1].y).toBeCloseTo(1, 2);
expect(peakList[1].width).toBeCloseTo(0.1, 2);
let optimizedPeaks = optimizePeaks(data, peakList);
expect(optimizedPeaks[0].x).toBeCloseTo(-0.5, 2);
expect(optimizedPeaks[0].y).toBeCloseTo(1, 2);
expect(optimizedPeaks[0].width).toBeCloseTo(0.2, 2); // optimization by default expect a gaussian shape
expect(optimizedPeaks[0].group).toBe(0);
expect(optimizedPeaks[1].x).toBeCloseTo(0.5, 2);
expect(optimizedPeaks[1].y).toBeCloseTo(1, 2);
expect(optimizedPeaks[1].width).toBeCloseTo(0.1, 2);
expect(optimizedPeaks[1].group).toBe(1);
});
it('Check gaussian shapes without specifying shape', () => {
const peaks = [
{ x: -0.5, y: 1, width: 0.2 },
{ x: 0.5, y: 1, width: 0.1 },
];
const data = generateSpectrum(peaks, { from: -1, to: 1, nbPoints: 10001 });
let peakList = gsd(data, {
minMaxRatio: 0,
realTopDetection: false,
smoothY: false,
heightFactor: 1,
});
expect(peakList[0].x).toBeCloseTo(-0.5, 2);
expect(peakList[0].y).toBeCloseTo(1, 2);
expect(peakList[0].width).toBeCloseTo(0.17, 2); // inflection points in gaussian are higher tha FWHM
expect(peakList[1].x).toBeCloseTo(0.5, 2);
expect(peakList[1].y).toBeCloseTo(1, 2);
expect(peakList[1].width).toBeCloseTo(0.085, 2);
let optimizedPeaks = optimizePeaks(data, peakList);
expect(optimizedPeaks[0].x).toBeCloseTo(-0.5, 2);
expect(optimizedPeaks[0].y).toBeCloseTo(1, 2);
expect(optimizedPeaks[0].width).toBeCloseTo(0.2, 2); // optimization by default expect a gaussian shape
expect(optimizedPeaks[0].group).toBe(0);
expect(optimizedPeaks[1].x).toBeCloseTo(0.5, 2);
expect(optimizedPeaks[1].y).toBeCloseTo(1, 2);
expect(optimizedPeaks[1].width).toBeCloseTo(0.1, 2);
expect(optimizedPeaks[1].group).toBe(1);
});
it('Should provide 1 peak', () => {
const peaks = [{ x: 0, y: 1, width: 0.12 }];
const data = generateSpectrum(peaks, {
from: -0.5,
to: 0.5,
nbPoints: 10001,
shape: {
kind: 'gaussian',
options: {
fwhm: 10000,
},
},
});
let peakList = gsd(data, {
minMaxRatio: 0,
realTopDetection: false,
smoothY: false,
heightFactor: 1,
shape: { kind: 'gaussian' },
});
expect(peakList).toHaveLength(1);
expect(peakList[0].x).toBeCloseTo(0, 2);
expect(peakList[0].y).toBeCloseTo(1, 2);
expect(peakList[0].width).toBeCloseTo(0.12, 3);
});
});
import { optimize } from 'ml-spectra-fitting';
import { xGetFromToIndex } from 'ml-spectra-processing';
import { groupPeaks } from './groupPeaks';
/**

@@ -10,3 +13,3 @@ * Optimize the position (x), max intensity (y), full width at half maximum (width)

* @param {number} [options.factorWidth = 1] - times of width to group peaks.
* @param {object} [options.joinPeaks = true] - if true the peaks could be grouped if the separation between them are inside of a range of factorWidth * width
* @param {number} [options.factorLimits = 2] - times of width to use to optimize peaks
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.

@@ -22,3 +25,3 @@ * @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.

factorWidth = 1,
joinPeaks = true,
factorLimits = 2,
shape = {

@@ -32,152 +35,33 @@ kind: 'gaussian',

let { x, y } = data;
let lastIndex = [0];
let groups = groupPeaks(peakList, factorWidth, joinPeaks);
let result = [];
let sampling;
for (let i = 0; i < groups.length; i++) {
let peaks = groups[i].group;
if (peaks.length > 1) {
// Multiple peaks
sampling = sampleFunction(
groups[i].limits[0] - groups[i].limits[1],
groups[i].limits[0] + groups[i].limits[1],
x,
y,
lastIndex,
);
if (sampling.x.length > 5) {
let { peaks: optPeaks } = optimize(sampling, peaks, {
shape,
optimization,
});
for (let j = 0; j < optPeaks.length; j++) {
optPeaks[j].index = peaks.index;
optPeaks[j].group = i;
result.push(optPeaks[j]);
}
}
} else {
// Single peak
peaks = peaks[0];
sampling = sampleFunction(
peaks.x - factorWidth * peaks.width,
peaks.x + factorWidth * peaks.width,
x,
y,
lastIndex,
);
if (sampling.x.length > 5) {
let fitResult = optimize(sampling, [peaks], {
shape,
optimization,
});
let { peaks: optPeaks } = fitResult;
optPeaks[0].index = peaks.index;
optPeaks[0].group = i;
result.push(optPeaks[0]);
}
}
if (data.x[0] > data.x[1]) {
data.x.reverse();
data.y.reverse();
}
return result;
}
function sampleFunction(from, to, x, y, lastIndex) {
let nbPoints = x.length;
let sampleX = [];
let sampleY = [];
let direction = Math.sign(x[1] - x[0]); // Direction of the derivative
if (direction === -1) {
lastIndex[0] = x.length - 1;
}
let groups = groupPeaks(peakList, factorWidth);
let delta = Math.abs(to - from) / 2;
let mid = (from + to) / 2;
let stop = false;
let index = lastIndex[0];
while (!stop && index < nbPoints && index >= 0) {
if (Math.abs(x[index] - mid) <= delta) {
sampleX.push(x[index]);
sampleY.push(y[index]);
index += direction;
} else {
// It is outside the range.
if (Math.sign(mid - x[index]) === 1) {
// We'll reach the mid going in the current direction
index += direction;
} else {
// There is not more peaks in the current range
stop = true;
}
}
}
lastIndex[0] = index;
return { x: sampleX, y: sampleY };
}
let results = [];
for (const peaks of groups) {
const firstPeak = peaks[0];
const lastPeak = peaks[peaks.length - 1];
function groupPeaks(peakList, nL, joinPeaks) {
let group = [];
let groups = [];
let limits = [peakList[0].x, nL * peakList[0].width];
let upperLimit, lowerLimit;
// Merge forward
for (let i = 0; i < peakList.length; i++) {
// If the 2 things overlaps
if (
joinPeaks &&
Math.abs(peakList[i].x - limits[0]) < nL * peakList[i].width + limits[1]
) {
// Add the peak to the group
group.push(peakList[i]);
// Update the group limits
upperLimit = limits[0] + limits[1];
if (peakList[i].x + nL * peakList[i].width > upperLimit) {
upperLimit = peakList[i].x + nL * peakList[i].width;
}
lowerLimit = limits[0] - limits[1];
if (peakList[i].x - nL * peakList[i].width < lowerLimit) {
lowerLimit = peakList[i].x - nL * peakList[i].width;
}
limits = [
(upperLimit + lowerLimit) / 2,
Math.abs(upperLimit - lowerLimit) / 2,
];
const from = firstPeak.x - firstPeak.width * factorLimits;
const to = lastPeak.x + lastPeak.width * factorLimits;
const { fromIndex, toIndex } = xGetFromToIndex(data.x, { from, to });
// Multiple peaks
const currentRange = {
x: data.x.slice(fromIndex, toIndex),
y: data.y.slice(fromIndex, toIndex),
};
if (currentRange.x.length > 5) {
let { peaks: optimizedPeaks } = optimize(currentRange, peaks, {
shape,
optimization,
});
results = results.concat(optimizedPeaks);
} else {
groups.push({ limits: limits, group: group });
group = [peakList[i]];
limits = [peakList[i].x, nL * peakList[i].width];
results = results.concat(peaks);
}
}
groups.push({ limits: limits, group: group });
// Merge backward
for (let i = groups.length - 2; i >= 0; i--) {
// The groups overlaps
if (
Math.abs(groups[i].limits[0] - groups[i + 1].limits[0]) <
(groups[i].limits[1] + groups[i + 1].limits[1]) / 2
) {
for (let j = 0; j < groups[i + 1].group.length; j++) {
groups[i].group.push(groups[i + 1].group[j]);
}
upperLimit = groups[i].limits[0] + groups[i].limits[1];
if (groups[i + 1].limits[0] + groups[i + 1].limits[1] > upperLimit) {
upperLimit = groups[i + 1].limits[0] + groups[i + 1].limits[1];
}
lowerLimit = groups[i].limits[0] - groups[i].limits[1];
if (groups[i + 1].limits[0] - groups[i + 1].limits[1] < lowerLimit) {
lowerLimit = groups[i + 1].limits[0] - groups[i + 1].limits[1];
}
groups[i].limits = [
(upperLimit + lowerLimit) / 2,
Math.abs(upperLimit - lowerLimit) / 2,
];
groups.splice(i + 1, 1);
}
}
return groups;
return results;
}
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