UNPKG

ml-gsd

Version:
165 lines (147 loc) 4.44 kB
import type { Shape1D } from 'ml-peak-shape-generator'; import type { OptimizationOptions } from 'ml-spectra-fitting'; import type { GSDPeak } from '../GSDPeak.ts'; import type { GSDPeakOptimized } from '../GSDPeakOptimized.ts'; import { addMissingIDs } from '../utils/addMissingIDs.ts'; import { addMissingShape } from '../utils/addMissingShape.ts'; import type { GSDPeakOptimizedID } from './optimizePeaksWithLogs.ts'; import { optimizePeaksWithLogs } from './optimizePeaksWithLogs.ts'; export interface JoinBroadPeaksOptions { /** * broadRatio * @default 0.0025 */ broadRatio?: number; /** * width limit to join peaks. * @default 0.25 */ broadWidth?: number; /** * it's specify the kind of shape used to fitting. */ shape?: Shape1D; /** * it's specify the kind and options of the algorithm use to optimize parameters. */ optimization?: OptimizationOptions; } /** * This function tries to join the peaks that seems to belong to a broad signal in a single broad peak. */ export type GSDPeakOptionalShape = GSDPeak & { shape?: Shape1D }; export function joinBroadPeaks( peakList: GSDPeakOptionalShape[], options: JoinBroadPeaksOptions = {}, ): GSDPeakOptimizedID[] { const { shape = { kind: 'gaussian' }, optimization = { kind: 'lm', options: { timeout: 10 } }, broadWidth = 0.25, broadRatio = 0.0025, } = options; let max = 0; let maxI = 0; let count = 1; const broadLines: GSDPeakOptionalShape[] = []; if (peakList.length < 2) { return addMissingIDs( addMissingShape(peakList.map(getGSDPeakOptimizedStructure), { shape }), ); } let maxDdy = peakList[0].ddY; for (let i = 1; i < peakList.length; i++) { if (Math.abs(peakList[i].ddY) > maxDdy) maxDdy = Math.abs(peakList[i].ddY); } const newPeaks: GSDPeakOptimized[] = []; for (const peak of peakList) { if (Math.abs(peak.ddY) <= broadRatio * maxDdy) { broadLines.push(peak); } else { newPeaks.push(getGSDPeakOptimizedStructure(peak)); } } //@ts-expect-error Push a feke peak broadLines.push({ x: Number.MAX_VALUE, y: 0 }); let candidates: { x: number[]; y: number[] } = { x: [broadLines[0].x], y: [broadLines[0].y], }; let indexes: number[] = [0]; for (let i = 1; i < broadLines.length; i++) { if (Math.abs(broadLines[i - 1].x - broadLines[i].x) < broadWidth) { candidates.x.push(broadLines[i].x); candidates.y.push(broadLines[i].y); if (broadLines[i].y > max) { max = broadLines[i].y; maxI = i; } indexes.push(i); count++; } else { if (count > 2) { const initialWidth = Math.abs( candidates.x[candidates.x.length - 1] - candidates.x[0], ); const { logs, optimizedPeaks } = optimizePeaksWithLogs( candidates, [ { id: crypto.randomUUID(), x: broadLines[maxI].x, y: max, width: initialWidth, parameters: { width: { max: initialWidth * 4, min: initialWidth * 0.8 }, }, }, ], { shape: { kind: 'pseudoVoigt' }, optimization }, ); [max, maxI] = [0, 0]; const log = logs.find((l) => l.message === 'optimization successful'); if (log) { const { error } = log; if (error < 0.2) { newPeaks.push(optimizedPeaks[0]); } else { pushBackPeaks(broadLines, indexes, newPeaks); } } else { pushBackPeaks(broadLines, indexes, newPeaks); } } else { pushBackPeaks(broadLines, indexes, newPeaks); } candidates = { x: [broadLines[i].x], y: [broadLines[i].y] }; indexes = [i]; max = broadLines[i].y; maxI = i; count = 1; } } newPeaks.sort((a, b) => { return a.x - b.x; }); return addMissingIDs(newPeaks, { output: newPeaks }); } function pushBackPeaks( broadLines: GSDPeakOptionalShape[], indexes: number[], peaks: GSDPeakOptimized[], ) { for (const index of indexes) { peaks.push(getGSDPeakOptimizedStructure(broadLines[index])); } } function getGSDPeakOptimizedStructure(peak: GSDPeakOptionalShape) { const { id, shape, x, y, width } = peak; const newPeak = { x, y, width, shape, } as GSDPeakOptimized; if (id) newPeak.id = id; return newPeak; }