Skip to content

Commit

Permalink
feat!: homogenize and generalize output (#63)
Browse files Browse the repository at this point in the history
* feat!: refactor gsd

homogenize output
rename base to noiseLevel
by default width is FWHM of a gaussian
remove properties 'left' and 'right'

* feat!: refactor broadenPeaks

homogenize output
remove properties 'from' and 'to'

* chore: refactor groupPeaks

make a copy

* feat!: refactor joinBroadPeaks

make a copy
homogenize output

* feat!: homogenize input/output optimizePeaks

* chore: adaptation of test cases

* chore: remove nodejs 10.x 15.x n add 16.x

* feat!: update ml-spectra-fitting to 2.0.0

* chore: use toBeClose instead of toStrictEqual
  • Loading branch information
jobo322 authored Oct 11, 2021
1 parent e2c843a commit 2b3a403
Show file tree
Hide file tree
Showing 21 changed files with 169 additions and 219 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
node-version: [12.x, 14.x, 16.x]
fail-fast: false
steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"eslint-fix": "npm run eslint -- --fix",
"compile": "rollup -c",
"prepack": "npm run compile",
"prettier": "prettier --check src",
"prettier-write": "prettier --write src",
"test": "npm run test-coverage && npm run eslint",
"test-only": "jest",
"test-coverage": "jest --coverage"
Expand Down Expand Up @@ -77,7 +79,7 @@
"cheminfo-types": "^0.5.0",
"ml-peak-shape-generator": "^2.0.2",
"ml-savitzky-golay-generalized": "2.0.3",
"ml-spectra-fitting": "^1.0.0",
"ml-spectra-fitting": "^2.0.0",
"ml-spectra-processing": "^6.8.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('Global spectra deconvolution NMR spectra', () => {
},
);

joinBroadPeaks(pp, { width: 0.25 });
pp = joinBroadPeaks(pp, { width: 0.25 });

expect(pp).toHaveLength(91);
});
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/broadNMR.js → src/__tests__/broadNMR.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ describe('Global spectra deconvolution NMR spectra', () => {
},
},
);
joinBroadPeaks(result, { width: 0.25, shape: { kind: 'lorentzian' } });
expect(result).toHaveLength(14);
result.forEach((peak) => {
const newResult = joinBroadPeaks(result, { width: 0.25, shape: { kind: 'lorentzian' } });
expect(newResult).toHaveLength(14);
newResult.forEach((peak) => {
if (Math.abs(peak.x - 4.31) < 0.01) {
expect(peak.width).toBeCloseTo(0.39, 2);
expect(peak.shape.width).toBeCloseTo(0.39, 2);
}
});
});
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 6 additions & 6 deletions src/__tests__/massPeakPicking.js → src/__tests__/massPeakPicking.test.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,26 @@ describe('Check the peak picking of a simulated mass spectrum', () => {
});
expect(result[0].x).toBeCloseTo(69.938, 1);
expect(result[0].y).toBeCloseTo(max, 2);
expect(result[0].width).toBeCloseTo(0.01, 4);
expect(result[0].shape.width).toBeCloseTo(0.01, 4);

expect(result[1].x).toBeCloseTo(71.935, 2);
expect(result[1].y).toBeCloseTo((63.99155 * max) / 100, 3);
expect(result[1].width).toBeCloseTo(0.01, 4);
expect(result[1].shape.width).toBeCloseTo(0.01, 4);

expect(result[2].x).toBeCloseTo(73.932, 1);
expect(result[2].y).toBeCloseTo((10.2373 * max) / 100, 2);
expect(result[2].width).toBeCloseTo(0.01, 4);
expect(result[2].shape.width).toBeCloseTo(0.01, 4);

expect(result[3].x).toBeCloseTo(157.837, 1);
expect(result[3].y).toBeCloseTo((51.39931 * max) / 100, 2);
expect(result[3].width).toBeCloseTo(0.01, 4);
expect(result[3].shape.width).toBeCloseTo(0.01, 4);

expect(result[4].x).toBeCloseTo(159.835, 1);
expect(result[4].y).toBeCloseTo(max, 2);
expect(result[4].width).toBeCloseTo(0.01, 4);
expect(result[4].shape.width).toBeCloseTo(0.01, 4);

expect(result[5].x).toBeCloseTo(161.833, 1);
expect(result[5].y).toBeCloseTo((48.63878 * max) / 100, 2);
expect(result[5].width).toBeCloseTo(0.01, 4);
expect(result[5].shape.width).toBeCloseTo(0.01, 4);
});
});
39 changes: 12 additions & 27 deletions src/__tests__/simple.js → src/__tests__/simple.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toMatchCloseTo } from 'jest-matcher-deep-close-to';
import { getShape1D } from 'ml-peak-shape-generator';

import { gsd } from '..';

Expand All @@ -25,6 +26,8 @@ describe('Simple test cases', () => {
negY.push(0);
}

let widthToFWHM = getShape1D('gaussian').widthToFWHM;

it('gsd not realtop', () => {
let peaks = gsd(
{ x, y },
Expand All @@ -39,8 +42,8 @@ describe('Simple test cases', () => {
);

expect(peaks[0].y).toBeCloseTo(4.657, 3);
expect(peaks[0].base).toBeCloseTo(1.1956, 3);
expect(peaks[0].x).toStrictEqual(15);
expect(peaks[0].shape.noiseLevel).toBeCloseTo(1.1956, 3);
expect(peaks[0].x).toBeCloseTo(15, 2);
});

it('gsd negative peak', () => {
Expand All @@ -57,8 +60,8 @@ describe('Simple test cases', () => {
},
);
expect(peaks[0].y).toBeCloseTo(-4.657, 3);
expect(peaks[0].base).toBeCloseTo(1.1956, 3);
expect(peaks[0].x).toStrictEqual(15);
expect(peaks[0].shape.noiseLevel).toBeCloseTo(1.1956, 3);
expect(peaks[0].x).toBeCloseTo(15,2);
});

it('gsd not realtop asymetric', () => {
Expand All @@ -79,18 +82,11 @@ describe('Simple test cases', () => {
expect(peaks).toMatchCloseTo(
[
{
base: 1.2434539324230613,
index: 15,
left: {
index: 13,
x: 13,
},
right: {
index: 16,
x: 16,
shape: {
noiseLevel: 1.2434539324230613,
soft: false,
width: widthToFWHM(3),
},
soft: false,
width: 3,
x: 15,
y: 5,
},
Expand All @@ -116,18 +112,7 @@ describe('Simple test cases', () => {
expect(peaks).toMatchCloseTo(
[
{
base: 1.2434539324230613,
index: 15,
left: {
index: 13,
x: 13,
},
right: {
index: 16,
x: 16,
},
soft: false,
width: 3,
shape: { noiseLevel: 1.2434539324230613, soft: false, width: widthToFWHM(3) },
x: 14.5,
y: 4.006546067576939,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toMatchCloseTo } from 'jest-matcher-deep-close-to';
import { getShape1D } from 'ml-peak-shape-generator';

import { gsd } from '..';

Expand Down Expand Up @@ -37,16 +38,16 @@ describe('Simple shifted baseline test cases', () => {
},
},
);
let widthToFWHM = getShape1D('gaussian').widthToFWHM;
expect(peaks).toHaveLength(1);
expect(peaks[0]).toMatchCloseTo({
index: 15,
x: 15,
y: 4.657142857142857,
width: 2,
soft: false,
left: { x: 14, index: 14 },
right: { x: 16, index: 16 },
base: 0.6695521174585716,
shape: {
width: widthToFWHM(2),
soft: false,
noiseLevel: 0.6695521174585716,
},
});
});

Expand All @@ -63,16 +64,16 @@ describe('Simple shifted baseline test cases', () => {
},
},
);
let widthToFWHM = getShape1D('gaussian').widthToFWHM;
expect(peaks).toHaveLength(1);
expect(peaks[0]).toMatchCloseTo({
index: 15,
x: 15,
y: -4.657142857142857,
width: 2,
soft: false,
left: { x: 14, index: 14 },
right: { x: 16, index: 16 },
base: 0.6695521174585716,
shape: {
width: widthToFWHM(2),
soft: false,
noiseLevel: 0.6695521174585716,
},
});
});
});
61 changes: 11 additions & 50 deletions src/__tests__/simulated.js → src/__tests__/simulated.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ describe('Global spectra deconvolution with simulated spectra', () => {

expect(peakList[0].x).toBeCloseTo(-0.1, 2);
expect(peakList[0].y).toBeCloseTo(0.2, 2);
expect(peakList[0].width).toBeCloseTo(0.03, 2);
expect(peakList[0].shape.width).toBeCloseTo(0.03, 2);
expect(peakList[1].x).toBeCloseTo(0.1, 2);
expect(peakList[1].y).toBeCloseTo(0.2, 2);
expect(peakList[1].width).toBeCloseTo(0.01, 2);
expect(peakList[1].shape.width).toBeCloseTo(0.01, 2);


expect(optimizedPeaks[0].x).toBeCloseTo(-0.1, 2);
expect(optimizedPeaks[0].y).toBeCloseTo(0.2, 2);
expect(optimizedPeaks[0].width).toBeCloseTo(0.03, 2);
expect(optimizedPeaks[0].group).toBe(0);
expect(optimizedPeaks[0].shape.width).toBeCloseTo(0.03, 2);
expect(optimizedPeaks[1].x).toBeCloseTo(0.1, 2);
expect(optimizedPeaks[1].y).toBeCloseTo(0.2, 2);
expect(optimizedPeaks[1].width).toBeCloseTo(0.01, 2);
expect(optimizedPeaks[1].group).toBe(1);
expect(optimizedPeaks[1].shape.width).toBeCloseTo(0.01, 2);
});

it('Check gaussian shapes with shape specification', () => {
Expand All @@ -62,62 +61,24 @@ describe('Global spectra deconvolution with simulated spectra', () => {
realTopDetection: false,
smoothY: false,
heightFactor: 1,
shape: { kind: 'gaussian' }, // we specifiy we are expecting a gaussian shape
});

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, {
generator: { from: -1, to: 1, nbPoints: 10001 },
});

let peakList = gsd(data, {
minMaxRatio: 0,
realTopDetection: false,
smoothY: false,
heightFactor: 1,
shape: { kind: 'gaussian' },
});

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[0].shape.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.085, 2);
expect(peakList[1].shape.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[0].shape.width).toBeCloseTo(0.2, 2); // optimization by default expect a gaussian shape
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);
expect(optimizedPeaks[1].shape.width).toBeCloseTo(0.1, 2);
});

it('Should provide 1 peak', () => {
Expand Down Expand Up @@ -145,6 +106,6 @@ describe('Global spectra deconvolution with simulated spectra', () => {
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);
expect(peakList[0].shape.width).toBeCloseTo(0.12, 3);
});
});
File renamed without changes.
File renamed without changes.
21 changes: 9 additions & 12 deletions src/gsd.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import SG from 'ml-savitzky-golay-generalized';
* @param {object} [options={}] - Options object
* @param {object} [options.shape={}] - Object that specified the kind of shape to calculate the FWHM instead of width between inflection points. see https://mljs.github.io/peak-shape-generator/#inflectionpointswidthtofwhm
* @param {object} [options.shape.kind='gaussian']
* @param {object} [options.shape.options={}]
* @param {object} [options.sgOptions] - Options object for Savitzky-Golay filter. See https://github.com/mljs/savitzky-golay-generalized#options
* @param {number} [options.sgOptions.windowSize = 9] - points to use in the approximations
* @param {number} [options.sgOptions.polynomial = 3] - degree of the polynomial to use in the approximations
Expand All @@ -32,7 +31,7 @@ export function gsd(data, options = {}) {
windowSize: 9,
polynomial: 3,
},
shape = {},
shape = { kind: 'gaussian' },
smoothY = true,
heightFactor = 0,
broadRatio = 0.0,
Expand Down Expand Up @@ -170,9 +169,7 @@ export function gsd(data, options = {}) {
}
}

let widthProcessor = shape.kind
? getShape1D(shape.kind, shape.options).widthToFWHM
: (x) => x;
let widthProcessor = getShape1D(shape.kind, shape.options).widthToFWHM;

let signals = [];
let lastK = -1;
Expand Down Expand Up @@ -209,17 +206,17 @@ export function gsd(data, options = {}) {
y: maxCriteria
? yData[minddY[j]] + noiseLevel
: -yData[minddY[j]] - noiseLevel,
width: widthProcessor(width),
soft: broadMask[j],
shape: {
kind: shape.kind,
width: widthProcessor(width),
soft: broadMask[j],
},
});

signals[signals.length - 1].left = intervalL[possible];
signals[signals.length - 1].right = intervalR[possible];

if (heightFactor) {
let yLeft = yData[intervalL[possible].index];
let yRight = yData[intervalR[possible].index];
signals[signals.length - 1].height =
signals[signals.length - 1].shape.height =
heightFactor *
(signals[signals.length - 1].y - (yLeft + yRight) / 2);
}
Expand All @@ -233,7 +230,7 @@ export function gsd(data, options = {}) {

// Correct the values to fit the original spectra data
for (let j = 0; j < signals.length; j++) {
signals[j].base = noiseLevel;
signals[j].shape.noiseLevel = noiseLevel;
}

signals.sort((a, b) => {
Expand Down
Loading

0 comments on commit 2b3a403

Please sign in to comment.