diff --git a/package.json b/package.json index 21c8546..868b3a0 100644 --- a/package.json +++ b/package.json @@ -79,10 +79,11 @@ "xy-parser": "^5.0.2" }, "dependencies": { + "@lukeed/uuid": "^2.0.0", "cheminfo-types": "^1.1.0", "ml-peak-shape-generator": "^4.1.1", "ml-savitzky-golay-generalized": "^4.0.1", - "ml-spectra-fitting": "^4.1.0", + "ml-spectra-fitting": "^4.1.1", "ml-spectra-processing": "^11.6.0" } } diff --git a/src/GSDBroadenPeak.ts b/src/GSDBroadenPeak.ts index 246a5ca..e1441cb 100644 --- a/src/GSDBroadenPeak.ts +++ b/src/GSDBroadenPeak.ts @@ -1,7 +1,11 @@ +import { Shape1D } from 'ml-peak-shape-generator'; + export interface GSDBroadenPeak { + id?: string; x: number; y: number; width: number; + shape?: Shape1D; index: number; from: { x: number }; to: { x: number }; diff --git a/src/GSDPeak.ts b/src/GSDPeak.ts index 2235eec..ffd0177 100644 --- a/src/GSDPeak.ts +++ b/src/GSDPeak.ts @@ -1,6 +1,7 @@ import { Shape1D } from 'ml-peak-shape-generator'; export interface GSDPeak { + id?: string; x: number; y: number; /** diff --git a/src/GSDPeakOptimized.ts b/src/GSDPeakOptimized.ts index 5d9e0b7..1da3d07 100644 --- a/src/GSDPeakOptimized.ts +++ b/src/GSDPeakOptimized.ts @@ -1,6 +1,7 @@ import { Shape1D } from 'ml-peak-shape-generator'; export interface GSDPeakOptimized { + id?: string; x: number; y: number; width: number; diff --git a/src/__tests__/gaussian-smooth.test.ts b/src/__tests__/gaussian-smooth.test.ts index 31a4123..bf994aa 100644 --- a/src/__tests__/gaussian-smooth.test.ts +++ b/src/__tests__/gaussian-smooth.test.ts @@ -33,6 +33,7 @@ describe('gaussian simulated peaks', () => { }); expect(peakList).toBeDeepCloseTo([ { + id: peakList[0].id, x: -0.5, y: 0.6945098953985852, ddY: -259.83290100626783, @@ -48,6 +49,7 @@ describe('gaussian simulated peaks', () => { }, }, { + id: peakList[1].id, x: 0.5, y: 0.6945098953985852, ddY: -259.83290100626783, diff --git a/src/__tests__/gaussian.test.ts b/src/__tests__/gaussian.test.ts index 86e7901..05dc4fa 100644 --- a/src/__tests__/gaussian.test.ts +++ b/src/__tests__/gaussian.test.ts @@ -63,7 +63,7 @@ describe('smooth:false option', () => { }, }, ]; - expect(peakList).toBeDeepCloseTo(expected); + expect(peakList).toBeDeepCloseTo(addMissingID(peakList, expected)); }); it('negative maxima peaks', () => { @@ -101,7 +101,7 @@ describe('smooth:false option', () => { }, }, ]; - expect(peakList).toBeDeepCloseTo(expected); + expect(peakList).toBeDeepCloseTo(addMissingID(peakList, expected)); }); it('Negative peaks', () => { @@ -143,7 +143,7 @@ describe('smooth:false option', () => { }, ]; - expect(peakList).toBeDeepCloseTo(expected); + expect(peakList).toBeDeepCloseTo(addMissingID(peakList, expected)); }); it('minima peaks', () => { @@ -185,7 +185,7 @@ describe('smooth:false option', () => { }, ]; - expect(peakList).toBeDeepCloseTo(expected); + expect(peakList).toBeDeepCloseTo(addMissingID(peakList, expected)); }); it('negative peaks with maxCriteria true', () => { @@ -196,3 +196,10 @@ describe('smooth:false option', () => { expect(peakList).toHaveLength(0); }); }); + +function addMissingID(peaks, expected) { + for (let i = 0; i < expected.length; i++) { + expected[i].id = peaks[i].id; + } + return expected; +} diff --git a/src/__tests__/power.test.ts b/src/__tests__/power.test.ts index 0ce2f4f..6c97e79 100644 --- a/src/__tests__/power.test.ts +++ b/src/__tests__/power.test.ts @@ -51,6 +51,7 @@ describe('power', () => { let peakList = gsd(data); let expected = [ { + id: peakList[0].id, x: 5, y: 10000, width: 0.3, @@ -77,6 +78,7 @@ describe('power', () => { // This shape is anyway quite strange const expected = [ { + id: peakList[0].id, x: 4.45, y: 7921, width: 0.05, @@ -92,6 +94,7 @@ describe('power', () => { }, }, { + id: peakList[1].id, x: 5, y: 10000, width: 0.2, @@ -107,6 +110,7 @@ describe('power', () => { }, }, { + id: peakList[2].id, x: 5.55, y: 7921, width: 0.05, diff --git a/src/__tests__/ubiquitin.test.ts b/src/__tests__/ubiquitin.test.ts index 9a555ab..3ba2646 100644 --- a/src/__tests__/ubiquitin.test.ts +++ b/src/__tests__/ubiquitin.test.ts @@ -22,7 +22,9 @@ describe('Global spectra deconvolution ubiquitin', () => { realTopDetection: true, sgOptions: { windowSize: 7, polynomial: 3 }, }); + expect(peaks[0]).toBeDeepCloseTo({ + id: peaks[0].id, x: 200.05527917306466, y: 28.795378784444413, ddY: -15468134.039875854, diff --git a/src/gsd.ts b/src/gsd.ts index fefcb09..f8ef7e4 100644 --- a/src/gsd.ts +++ b/src/gsd.ts @@ -1,3 +1,4 @@ +import { v4 as generateID } from '@lukeed/uuid'; import type { DataXY } from 'cheminfo-types'; import { Shape1D } from 'ml-peak-shape-generator'; import { sgg, SGGOptions } from 'ml-savitzky-golay-generalized'; @@ -10,6 +11,8 @@ import { } from 'ml-spectra-processing'; import { GSDPeak } from './GSDPeak'; +import { MakeMandatory } from './utils/MakeMandatory'; +import { MakeOptional } from './utils/MakeOptional'; import { optimizeTop } from './utils/optimizeTop'; import { setShape } from './utils/setShape'; @@ -52,7 +55,8 @@ export interface GSDOptions { */ shape?: Shape1D; } - +export type GSDPeakID = MakeMandatory; +export type GSDPeakIDOptionalShape = MakeOptional; /** * Global spectra deconvolution * @param data - Object data with x and y arrays. Values in x has to be growing @@ -61,7 +65,7 @@ export interface GSDOptions { */ -export function gsd(data: DataXY, options: GSDOptions = {}): GSDPeak[] { +export function gsd(data: DataXY, options: GSDOptions = {}): GSDPeakID[] { let { sgOptions = { windowSize: 9, @@ -217,7 +221,7 @@ export function gsd(data: DataXY, options: GSDOptions = {}): GSDPeak[] { let lastK = -1; - const peaks: GSDPeak[] = []; + const peaks: GSDPeakIDOptionalShape[] = []; for (const minddYIndex of minddY) { let deltaX = x[minddYIndex]; let possible = -1; @@ -245,6 +249,7 @@ export function gsd(data: DataXY, options: GSDOptions = {}): GSDPeak[] { if (yData[minddYIndex] > yThreshold) { let width = Math.abs(intervalR[possible].x - intervalL[possible].x); peaks.push({ + id: generateID(), x: deltaX, y: yData[minddYIndex], width, @@ -254,7 +259,7 @@ export function gsd(data: DataXY, options: GSDOptions = {}): GSDPeak[] { from: intervalL[possible], to: intervalR[possible], }, - } as GSDPeak); + }); } } } @@ -274,5 +279,5 @@ export function gsd(data: DataXY, options: GSDOptions = {}): GSDPeak[] { return a.x - b.x; }); - return setShape(peaks, { shape }); + return setShape(peaks, { shape }) as GSDPeakID[]; } diff --git a/src/post/__tests__/broadenPeaks.test.ts b/src/post/__tests__/broadenPeaks.test.ts index 717e213..c99174a 100644 --- a/src/post/__tests__/broadenPeaks.test.ts +++ b/src/post/__tests__/broadenPeaks.test.ts @@ -300,6 +300,7 @@ describe('broadenPeaks', () => { let result = broadenPeaks( [ { + id: '1', x: -0.5, y: 1, ddY: 0, @@ -327,6 +328,7 @@ describe('broadenPeaks', () => { ddY: 0, width: 0.08, index: 575, + shape: { kind: 'gaussian' }, inflectionPoints: { from: { index: 573, x: 10.46 }, to: { index: 577, x: 10.54 }, @@ -335,31 +337,36 @@ describe('broadenPeaks', () => { ], { factor: 20 }, ); - expect(result).toBeDeepCloseTo([ - { - x: -0.5, - y: 1, - index: 25, - width: 1.3, - from: { x: -1.3 }, - to: { x: 0 }, - }, - { - x: 0.5, - y: 1, - index: 75, - width: 1.3, - from: { x: 0 }, - to: { x: 1.3 }, - }, - { - x: 10.5, - y: 1, - index: 575, - width: 1.6, - from: { x: 9.7 }, - to: { x: 11.3 }, - }, - ]); + expect(result).toBeDeepCloseTo( + [ + { + id: '1', + x: -0.5, + y: 1, + index: 25, + width: 1.3, + from: { x: -1.3 }, + to: { x: 0 }, + }, + { + x: 0.5, + y: 1, + index: 75, + width: 1.3, + from: { x: 0 }, + to: { x: 1.3 }, + }, + { + x: 10.5, + y: 1, + index: 575, + shape: { kind: 'gaussian' }, + width: 1.6, + from: { x: 9.7 }, + to: { x: 11.3 }, + }, + ], + 1, + ); }); }); diff --git a/src/post/__tests__/joinBroadPeaks.test.ts b/src/post/__tests__/joinBroadPeaks.test.ts index 70cbede..6f2dbec 100644 --- a/src/post/__tests__/joinBroadPeaks.test.ts +++ b/src/post/__tests__/joinBroadPeaks.test.ts @@ -26,7 +26,6 @@ describe('joinBroadPeaks', () => { broadRatio: 0.03, broadWidth: 0.25, }); - expect(peaks.length).toBeGreaterThan(3); expect(newPeaks).toHaveLength(3); }); diff --git a/src/post/broadenPeaks.ts b/src/post/broadenPeaks.ts index c992c6a..7435981 100644 --- a/src/post/broadenPeaks.ts +++ b/src/post/broadenPeaks.ts @@ -1,5 +1,21 @@ +import { getShape1D, Shape1D } from 'ml-peak-shape-generator'; + import { GSDBroadenPeak } from '../GSDBroadenPeak'; import { GSDPeak } from '../GSDPeak'; +import { MakeOptional } from '../utils/MakeOptional'; + +type GSDPeakOptionalShape = MakeOptional; + +type GSDBroadenPeakWithID = GSDBroadenPeak & { id: string }; +type GSDBroadenPeakWithShape = GSDBroadenPeak & { shape: Shape1D }; +type GSDBroadenPeakWithShapeID = GSDBroadenPeakWithID & { shape: Shape1D }; + +export type WithOrWithout = + T extends ToExtends ? TrueType : FalseType; + +export type WithIDOrShape = T extends { id: string } + ? WithOrWithout + : WithOrWithout; /** * This method will allow to enlarge peaks while preventing overlap between peaks @@ -9,8 +25,8 @@ import { GSDPeak } from '../GSDPeak'; * @return {Array} peakList */ -export function broadenPeaks( - peakList: Omit[], +export function broadenPeaks( + peakList: T[], options: { /** * @default 2 @@ -22,21 +38,10 @@ export function broadenPeaks( */ overlap?: boolean; } = {}, -): GSDBroadenPeak[] { +) { const { factor = 2, overlap = false } = options; - const peaks = peakList.map((peak) => { - const xFrom = peak.x - (peak.x - peak.inflectionPoints.from.x) * factor; - const xTo = peak.x + (peak.inflectionPoints.to.x - peak.x) * factor; - return { - x: peak.x, - y: peak.y, - index: peak.index, - width: xTo - xFrom, - from: { x: xFrom }, - to: { x: xTo }, - }; - }); + const peaks = mapPeaks(peakList, factor); if (!overlap) { for (let i = 0; i < peaks.length - 1; i++) { @@ -51,9 +56,48 @@ export function broadenPeaks( } } - for (let peak of peaks) { + for (const peak of peaks) { peak.width = peak.to.x - peak.from.x; + if (peak.shape) { + const { shape, width } = peak; + if (shape.fwhm !== undefined) { + const shapeFct = getShape1D(shape); + peak.shape.fwhm = shapeFct.widthToFWHM(width); + } + } } return peaks; } + +function mapPeaks( + peaks: T[], + factor: number, +): WithIDOrShape[] { + return peaks.map((peak) => { + const { id, shape } = peak; + const xFrom = peak.x - (peak.x - peak.inflectionPoints.from.x) * factor; + const xTo = peak.x + (peak.inflectionPoints.to.x - peak.x) * factor; + + let result = { + x: peak.x, + y: peak.y, + index: peak.index, + width: xTo - xFrom, + from: { x: xFrom }, + to: { x: xTo }, + } as GSDBroadenPeak; + + if (id) { + result = { ...result, id } as GSDBroadenPeakWithID; + } + + if (shape) { + result = { ...result, shape } as T extends { id: string } + ? GSDBroadenPeakWithShapeID + : GSDBroadenPeakWithShape; + } + + return result as WithIDOrShape; + }); +} diff --git a/src/post/joinBroadPeaks.ts b/src/post/joinBroadPeaks.ts index 030e880..fd8e257 100644 --- a/src/post/joinBroadPeaks.ts +++ b/src/post/joinBroadPeaks.ts @@ -1,11 +1,15 @@ +import { v4 as generateID } from '@lukeed/uuid'; import type { Shape1D } from 'ml-peak-shape-generator'; import { OptimizationOptions } from 'ml-spectra-fitting'; import { GSDPeak } from '../GSDPeak'; import { GSDPeakOptimized } from '../GSDPeakOptimized'; +import { MakeOptional } from '../utils/MakeOptional'; +import { addMissingIDs } from '../utils/addMissingIDs'; import { addMissingShape } from '../utils/addMissingShape'; import { optimizePeaks } from './optimizePeaks'; +import { GSDPeakOptimizedID } from './optimizePeaksWithLogs'; export interface JoinBroadPeaksOptions { /** @@ -32,13 +36,12 @@ export interface JoinBroadPeaksOptions { * This function tries to join the peaks that seems to belong to a broad signal in a single broad peak. */ -export interface GSDPeakWithOptionalShape extends Omit { - shape?: Shape1D; -} -export function joinBroadPeaks( - peakList: GSDPeakWithOptionalShape[], +export type GSDPeakOptionalShape = MakeOptional; + +export function joinBroadPeaks( + peakList: T[], options: JoinBroadPeaksOptions = {}, -): GSDPeak[] { +): GSDPeakOptimizedID[] { let { shape = { kind: 'gaussian' }, optimization = { kind: 'lm', options: { timeout: 10 } }, @@ -49,19 +52,25 @@ export function joinBroadPeaks( let max = 0; let maxI = 0; let count = 1; - const broadLines: GSDPeakOptimized[] = []; - const peaks = addMissingShape(peakList, { shape }); + const broadLines: T[] = []; - if (peaks.length < 2) return peaks; + 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); } - for (let i: number = peaks.length - 1; i >= 0; i--) { - if (Math.abs(peaks[i].ddY) <= broadRatio * maxDdy) { - broadLines.push(peaks.splice(i, 1)[0]); + const newPeaks: GSDPeakOptimized[] = []; + for (const peak of peakList) { + if (Math.abs(peak.ddY) <= broadRatio * maxDdy) { + broadLines.push(peak); + } else { + newPeaks.push(getGSDPeakOptimizedStructure(peak)); } } @@ -89,6 +98,7 @@ export function joinBroadPeaks( candidates, [ { + id: generateID(), x: broadLines[maxI].x, y: max, width: candidates.x[0] - candidates.x[candidates.x.length - 1], @@ -96,14 +106,12 @@ export function joinBroadPeaks( ], { shape, optimization }, ); - //@ts-expect-error type is equal as expected - peaks.push(fitted[0]); + newPeaks.push(fitted[0]); } else { // Put back the candidates to the peak list - indexes.forEach((index) => { - // @ts-expect-error todo 2 - peaks.push(broadLines[index]); - }); + for (const index of indexes) { + newPeaks.push(getGSDPeakOptimizedStructure(broadLines[index])); + } } candidates = { x: [broadLines[i].x], y: [broadLines[i].y] }; @@ -113,9 +121,24 @@ export function joinBroadPeaks( count = 1; } } - peaks.sort((a, b) => { + newPeaks.sort((a, b) => { return a.x - b.x; }); - return peaks; + return addMissingIDs(newPeaks, { output: newPeaks }); +} + +function getGSDPeakOptimizedStructure(peak: T) { + const { id, shape, x, y, width } = peak; + + let newPeak = { + x, + y, + width, + shape, + } as GSDPeakOptimized; + + if (id) newPeak.id = id; + + return newPeak; } diff --git a/src/post/optimizePeaks.ts b/src/post/optimizePeaks.ts index c04c8c2..7397a99 100644 --- a/src/post/optimizePeaks.ts +++ b/src/post/optimizePeaks.ts @@ -1,11 +1,9 @@ -import type { DataXY, PeakXYWidth } from 'cheminfo-types'; +import type { DataXY } from 'cheminfo-types'; import { FromTo } from 'cheminfo-types'; import { Shape1D } from 'ml-peak-shape-generator'; import type { OptimizationOptions } from 'ml-spectra-fitting'; -import { GSDPeakOptimized } from '../GSDPeakOptimized'; - -import { optimizePeaksWithLogs } from './optimizePeaksWithLogs'; +import { Peak, optimizePeaksWithLogs } from './optimizePeaksWithLogs'; export interface OptimizePeaksOptions { /** @@ -43,10 +41,10 @@ export interface OptimizePeaksOptions { * @param data - An object containing the x and y data to be fitted. * @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}]. */ -export function optimizePeaks( +export function optimizePeaks( data: DataXY, - peakList: PeakXYWidth[], + peakList: T[], options: OptimizePeaksOptions = {}, -): GSDPeakOptimized[] { +) { return optimizePeaksWithLogs(data, peakList, options).optimizedPeaks; } diff --git a/src/post/optimizePeaksWithLogs.ts b/src/post/optimizePeaksWithLogs.ts index 640e668..8f12211 100644 --- a/src/post/optimizePeaksWithLogs.ts +++ b/src/post/optimizePeaksWithLogs.ts @@ -4,22 +4,35 @@ import { optimize } from 'ml-spectra-fitting'; import { xGetFromToIndex } from 'ml-spectra-processing'; import { GSDPeakOptimized } from '../GSDPeakOptimized'; +import { MakeMandatory } from '../utils/MakeMandatory'; import { addMissingShape } from '../utils/addMissingShape'; import { groupPeaks } from '../utils/groupPeaks'; import { OptimizePeaksOptions } from './optimizePeaks'; +export interface Peak extends PeakXYWidth { + id?: string; +} + +export type GSDPeakOptimizedID = MakeMandatory; + +type GSDPeakOptimizedIDOrNot = T extends { + id: string; +} + ? GSDPeakOptimizedID + : GSDPeakOptimized; + /** * Optimize the position (x), max intensity (y), full width at half maximum (fwhm) * and the ratio of gaussian contribution (mu) if it's required. It currently supports three kind of shapes: gaussian, lorentzian and pseudovoigt * @param data - An object containing the x and y data to be fitted. * @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}]. */ -export function optimizePeaksWithLogs( +export function optimizePeaksWithLogs( data: DataXY, - peakList: PeakXYWidth[], + peakList: T[], options: OptimizePeaksOptions = {}, -): { logs: any[]; optimizedPeaks: GSDPeakOptimized[] } { +): { logs: any[]; optimizedPeaks: GSDPeakOptimizedIDOrNot[] } { const { fromTo = {}, baseline, @@ -41,7 +54,7 @@ export function optimizePeaksWithLogs( */ let groups = groupPeaks(peakList, { factor: groupingFactor }); let logs: any[] = []; - let results: GSDPeakOptimized[] = []; + let results: GSDPeakOptimizedIDOrNot[] = []; groups.forEach((peakGroup) => { const start = Date.now(); // In order to make optimization we will add fwhm and shape on all the peaks @@ -90,7 +103,7 @@ export function optimizePeaksWithLogs( width: getShape1D(peaks[i].shape).fwhmToWidth( optimizedPeaks[i].shape.fwhm, ), - }); + } as GSDPeakOptimizedIDOrNot); } logs.push({ ...log, @@ -99,7 +112,7 @@ export function optimizePeaksWithLogs( message: 'optimization successful', }); } else { - results = results.concat(peaks); + results.push(...(peaks as GSDPeakOptimizedIDOrNot[])); logs.push({ ...log, iterations: 0, diff --git a/src/utils/GSDPeakShape.ts b/src/utils/GSDPeakShape.ts deleted file mode 100644 index 70b6b46..0000000 --- a/src/utils/GSDPeakShape.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Shape1D } from 'ml-peak-shape-generator'; - -import { GSDPeak } from '../GSDPeak'; - -export interface GSDPeakShape extends GSDPeak { - fwhm: number; - shape: Shape1D; -} diff --git a/src/utils/MakeMandatory.ts b/src/utils/MakeMandatory.ts new file mode 100644 index 0000000..c5849bb --- /dev/null +++ b/src/utils/MakeMandatory.ts @@ -0,0 +1 @@ +export type MakeMandatory = T & { [P in K]-?: T[P] }; diff --git a/src/utils/MakeOptional.ts b/src/utils/MakeOptional.ts new file mode 100644 index 0000000..d585157 --- /dev/null +++ b/src/utils/MakeOptional.ts @@ -0,0 +1,3 @@ +export type MakeOptional = Omit & { + [P in K]?: T[P] | undefined; +}; diff --git a/src/utils/addMissingIDs.ts b/src/utils/addMissingIDs.ts new file mode 100644 index 0000000..00a14e5 --- /dev/null +++ b/src/utils/addMissingIDs.ts @@ -0,0 +1,17 @@ +import { v4 as generateID } from '@lukeed/uuid'; + +const { parse, stringify } = JSON; + +export function addMissingIDs( + peaks: T[], + options: { output?: T[] } = {}, +) { + const { output = parse(stringify(peaks)) as T[] } = options; + for (const peak of output) { + if (!('id' in peak)) { + peak.id = generateID(); + } + } + + return output as (T & { id: string })[]; +} diff --git a/src/utils/addMissingShape.ts b/src/utils/addMissingShape.ts index effb93a..0ad6c09 100644 --- a/src/utils/addMissingShape.ts +++ b/src/utils/addMissingShape.ts @@ -1,17 +1,19 @@ import { getShape1D, Shape1D } from 'ml-peak-shape-generator'; +const { parse, stringify } = JSON; /** * add missing property if it does not exist in the peak, * if shape exists but fwhm doesn't, it will be calculated from peak.width */ -export function addMissingShape( +export function addMissingShape( peaks: T[], - options: { shape?: Shape1D } = {}, + options: { shape?: Shape1D; output?: T[] } = {}, ): (T & { shape: Shape1D })[] { - const { shape = { kind: 'gaussian' } } = options; + const { shape = { kind: 'gaussian' }, output = parse(stringify(peaks)) } = + options; let shapeInstance = getShape1D(shape); - return peaks.map((peak) => { + return output.map((peak) => { if (hasShape(peak)) { if (!('fwhm' in peak.shape)) { const shapeInstance = getShape1D(peak.shape); diff --git a/src/utils/setShape.ts b/src/utils/setShape.ts index 84ffbcf..4872372 100644 --- a/src/utils/setShape.ts +++ b/src/utils/setShape.ts @@ -1,5 +1,7 @@ import { getShape1D, Shape1D } from 'ml-peak-shape-generator'; +const { parse, stringify } = JSON; + /** * Append 2 properties to the peaks, shape and fwhm */ @@ -12,11 +14,16 @@ export function setShape( * @default "{ kind: 'gaussian' }" */ shape?: Shape1D; + output?: T[]; } = {}, -) { - let { shape = { kind: 'gaussian' } } = options; +): (T & { shape: Shape1D })[] { + let { + shape = { kind: 'gaussian' }, + output = parse(stringify(peaks)) as T[], + } = options; let shapeInstance = getShape1D(shape); - return peaks.map((peak) => ({ + + return output.map((peak) => ({ ...peak, shape: { fwhm: shapeInstance.widthToFWHM(peak.width), ...shape }, }));