UNPKG

ml-gsd

Version:
164 lines 5.39 kB
import { sgg } from 'ml-savitzky-golay-generalized'; import { xIsEquallySpaced, xIsMonotonic, xMinMaxValues, xNoiseStandardDeviation, } from 'ml-spectra-processing'; import { optimizeTop } from "./utils/optimizeTop.js"; /** * Global spectra deconvolution * @param data - Object data with x and y arrays. Values in x has to be growing * @param options * @param {number} [options.broadRatio = 0.00] - If `broadRatio` is higher than 0, then all the peaks which second derivative * smaller than `broadRatio * maxAbsSecondDerivative` will be marked with the soft mask equal to true. */ export function gsd(data, options = {}) { let { noiseLevel } = options; const { sgOptions = { windowSize: 9, polynomial: 3, }, smoothY = false, maxCriteria = true, minMaxRatio = 0.00025, realTopDetection = false, } = options; const { x } = data; let { y } = data; if (xIsMonotonic(x) !== 1) { throw new Error('GSD only accepts monotone increasing x values'); } //rescale; y = y.slice(); // If the max difference between delta x is less than 5%, then, // we can assume it to be equally spaced variable const equallySpaced = xIsEquallySpaced(x); if (noiseLevel === undefined) { if (equallySpaced) { const noiseInfo = xNoiseStandardDeviation(y); if (maxCriteria) { noiseLevel = noiseInfo.median + 1.5 * noiseInfo.sd; } else { noiseLevel = -noiseInfo.median + 1.5 * noiseInfo.sd; } } else { noiseLevel = 0; } } else if (!maxCriteria) { noiseLevel *= -1; } if (!maxCriteria) { for (let i = 0; i < y.length; i++) { y[i] *= -1; } } if (noiseLevel !== undefined) { for (let i = 0; i < y.length; i++) { if (y[i] < noiseLevel) { y[i] = noiseLevel; } } } const xValue = equallySpaced ? x[1] - x[0] : x; const yData = smoothY ? sgg(y, xValue, { ...sgOptions, derivative: 0, }) : y; const dY = sgg(y, xValue, { ...sgOptions, derivative: 1, }); const ddY = sgg(y, xValue, { ...sgOptions, derivative: 2, }); const { min: minY, max: maxY } = xMinMaxValues(yData); if (minY > maxY || minY === maxY) return []; const yThreshold = minY + (maxY - minY) * minMaxRatio; const dX = x[1] - x[0]; let lastMax = null; let lastMin = null; const minddY = []; const intervalL = []; const intervalR = []; // By the intermediate value theorem We cannot find 2 consecutive maximum or minimum for (let i = 1; i < yData.length - 1; ++i) { if ((dY[i] < dY[i - 1] && dY[i] <= dY[i + 1]) || (dY[i] <= dY[i - 1] && dY[i] < dY[i + 1])) { lastMin = { x: x[i], index: i, }; if (dX > 0 && lastMax !== null) { intervalL.push(lastMax); intervalR.push(lastMin); } } // Maximum in first derivative if ((dY[i] >= dY[i - 1] && dY[i] > dY[i + 1]) || (dY[i] > dY[i - 1] && dY[i] >= dY[i + 1])) { lastMax = { x: x[i], index: i, }; if (dX < 0 && lastMin !== null) { intervalL.push(lastMax); intervalR.push(lastMin); } } // Minimum in second derivative if (ddY[i] < ddY[i - 1] && ddY[i] < ddY[i + 1]) { minddY.push(i); } } let lastK = -1; const peaks = []; for (const minddYIndex of minddY) { const deltaX = x[minddYIndex]; let possible = -1; let k = lastK + 1; let minDistance = Number.POSITIVE_INFINITY; let currentDistance = 0; while (possible === -1 && k < intervalL.length) { currentDistance = Math.abs(deltaX - (intervalL[k].x + intervalR[k].x) / 2); if (currentDistance < (intervalR[k].x - intervalL[k].x) / 2) { possible = k; lastK = k; } ++k; // Not getting closer? if (currentDistance >= minDistance) { break; } minDistance = currentDistance; } if (possible !== -1) { if (yData[minddYIndex] > yThreshold) { const width = Math.abs(intervalR[possible].x - intervalL[possible].x); peaks.push({ id: crypto.randomUUID(), x: deltaX, y: yData[minddYIndex], width, index: minddYIndex, ddY: ddY[minddYIndex], inflectionPoints: { from: intervalL[possible], to: intervalR[possible], }, }); } } } if (realTopDetection) { optimizeTop({ x, y: yData }, peaks); } peaks.forEach((peak) => { if (!maxCriteria) { peak.y *= -1; peak.ddY = peak.ddY * -1; } }); peaks.sort((a, b) => { return a.x - b.x; }); return peaks; } //# sourceMappingURL=gsd.js.map