-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add optimizePeaksWithLogs to be able to debug
- Loading branch information
Showing
4 changed files
with
171 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { toMatchCloseTo } from 'jest-matcher-deep-close-to'; | ||
import { generateSpectrum } from 'spectrum-generator'; | ||
|
||
import { optimizePeaksWithLogs } from '../optimizePeaksWithLogs'; | ||
|
||
expect.extend({ toMatchCloseTo }); | ||
|
||
describe('optimizePeaksWithLogs', () => { | ||
it('Should throw because execution time is over timeout', () => { | ||
const peaks = [{ x: 0, y: 1, width: 0.12 }]; | ||
|
||
const data = generateSpectrum(peaks, { | ||
generator: { | ||
from: -0.5, | ||
to: 0.5, | ||
nbPoints: 101, | ||
shape: { | ||
kind: 'gaussian', | ||
}, | ||
}, | ||
}); | ||
|
||
let result = optimizePeaksWithLogs(data, [ | ||
{ | ||
x: 0.01, | ||
y: 0.9, | ||
width: 0.11, | ||
}, | ||
]); | ||
expect(result.logs).toMatchObject([ | ||
{ | ||
iterations: 100, | ||
error: 0.000017852930772995625, | ||
parameters: { kind: 'lm', options: { timeout: 10 } }, | ||
message: 'optimization successful', | ||
groupSize: 1, | ||
}, | ||
]); | ||
expect(result.optimizedPeaks).toMatchCloseTo([ | ||
{ | ||
x: 0, | ||
y: 1, | ||
width: 0.12, | ||
shape: { | ||
kind: 'gaussian', | ||
fwhm: 0.14128970668640126, | ||
}, | ||
}, | ||
]); | ||
|
||
const options = { | ||
optimization: { | ||
kind: 'lm' as const, | ||
options: { | ||
timeout: 0, | ||
}, | ||
}, | ||
}; | ||
expect(() => | ||
optimizePeaksWithLogs(data, [{ x: 0.1, y: 0.9, width: 0.11 }], options), | ||
).toThrow('The execution time is over to 0 seconds'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import type { DataXY, PeakXYWidth } from 'cheminfo-types'; | ||
import { getShape1D } from 'ml-peak-shape-generator'; | ||
import { optimize } from 'ml-spectra-fitting'; | ||
import { xGetFromToIndex } from 'ml-spectra-processing'; | ||
|
||
import { GSDPeakOptimized } from '../GSDPeakOptimized'; | ||
import { appendShapeAndFWHM } from '../utils/appendShapeAndFWHM'; | ||
import { groupPeaks } from '../utils/groupPeaks'; | ||
|
||
import { OptimizePeaksOptions } from './optimizePeaks'; | ||
|
||
/** | ||
* 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( | ||
data: DataXY, | ||
peakList: PeakXYWidth[], | ||
options: OptimizePeaksOptions = {}, | ||
): { logs: any[]; optimizedPeaks: GSDPeakOptimized[] } { | ||
const { | ||
shape = { kind: 'gaussian' }, | ||
groupingFactor = 1, | ||
factorLimits = 2, | ||
optimization = { | ||
kind: 'lm', | ||
options: { | ||
timeout: 10, | ||
}, | ||
}, | ||
}: OptimizePeaksOptions = options; | ||
|
||
/* | ||
The optimization algorithm will take some group of peaks. | ||
We can not simply optimize everything because there would be too many variables to optimize | ||
and it would be too time consuming. | ||
*/ | ||
let groups = groupPeaks(peakList, { factor: groupingFactor }); | ||
let logs: any[] = []; | ||
let results: GSDPeakOptimized[] = []; | ||
|
||
groups.forEach((peakGroup) => { | ||
const start = Date.now(); | ||
// In order to make optimization we will add fwhm and shape on all the peaks | ||
const peaks = appendShapeAndFWHM(peakGroup, { shape }); | ||
|
||
const firstPeak = peaks[0]; | ||
const lastPeak = peaks[peaks.length - 1]; | ||
|
||
const from = firstPeak.x - firstPeak.width * factorLimits; | ||
const to = lastPeak.x + lastPeak.width * factorLimits; | ||
const { fromIndex, toIndex } = xGetFromToIndex(data.x, { from, to }); | ||
|
||
const x = | ||
data.x instanceof Float64Array | ||
? data.x.subarray(fromIndex, toIndex) | ||
: data.x.slice(fromIndex, toIndex); | ||
const y = | ||
data.y instanceof Float64Array | ||
? data.y.subarray(fromIndex, toIndex) | ||
: data.y.slice(fromIndex, toIndex); | ||
|
||
if (x.length > 5) { | ||
const { | ||
iterations, | ||
error, | ||
peaks: optimizedPeaks, | ||
} = optimize({ x, y }, peaks, { | ||
shape, | ||
optimization, | ||
}); | ||
|
||
for (let i = 0; i < peaks.length; i++) { | ||
results.push({ | ||
...optimizedPeaks[i], | ||
width: getShape1D(peaks[i].shape).fwhmToWidth( | ||
optimizedPeaks[i].shape.fwhm, | ||
), | ||
}); | ||
} | ||
logs.push({ | ||
iterations, | ||
error, | ||
parameters: optimization, | ||
message: 'optimization successful', | ||
groupSize: peakGroup.length, | ||
time: `${Date.now() - start}ms`, | ||
}); | ||
} else { | ||
results = results.concat(peaks); | ||
logs.push({ | ||
iterations: 0, | ||
message: 'x length too small for optimization', | ||
groupSize: peakGroup.length, | ||
time: `${Date.now() - start}ms`, | ||
}); | ||
} | ||
}); | ||
|
||
return { logs, optimizedPeaks: results }; | ||
} |