diff --git a/package-lock.json b/package-lock.json index 2f07b10f1..90192b507 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "ml-array-mean": "^2.0.0", "ml-array-median": "^2.0.0", "ml-conrec": "^6.0.0", - "ml-gsd": "^14.0.1", + "ml-gsd": "^14.1.0", "ml-signal-processing": "^2.2.1", "ml-spectra-processing": "^14.29.0", "ml-tree-similarity": "^2.2.0", @@ -3900,16 +3900,25 @@ "license": "MIT" }, "node_modules/@zakodium/nmr-types": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/@zakodium/nmr-types/-/nmr-types-0.5.12.tgz", - "integrity": "sha512-aAYP4DTncj6PamSnSsFa8HOyE5lHT6pGtk4fAyTmMRJIvlZrK8qoQdEz4lulfhPwkPiPB6ameKm/QK8F8f4kxg==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@zakodium/nmr-types/-/nmr-types-0.5.14.tgz", + "integrity": "sha512-TEgAajDAvfGSafwQBCEesrfDJVpTq7bfRnkorLKXiflNLJzjpsIk68sgm3NfVuo7Dv7d0kTmgNAbKk0gpX66bQ==", "license": "CC-BY-NC-SA-4.0", "dependencies": { - "ml-peak-shape-generator": "^4.2.0", + "ml-peak-shape-generator": "^5.1.0", "ml-signal-processing": "^2.2.1", "ml-spectra-processing": "^14.28.1" } }, + "node_modules/@zakodium/nmr-types/node_modules/ml-peak-shape-generator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ml-peak-shape-generator/-/ml-peak-shape-generator-5.1.0.tgz", + "integrity": "sha512-W2Vx/+R65zyr/B13p5TLyqwjTgIzwR0xtjRyQ2ZSxDHdVaXxb6m+ajyyaHJqGG5mYzeqi97nGvpklhz0jrsbrg==", + "license": "MIT", + "dependencies": { + "cheminfo-types": "^1.15.0" + } + }, "node_modules/@zakodium/nmrium-core": { "version": "0.7.24", "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core/-/nmrium-core-0.7.24.tgz", @@ -9244,18 +9253,27 @@ } }, "node_modules/ml-gsd": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/ml-gsd/-/ml-gsd-14.0.1.tgz", - "integrity": "sha512-cvWt1TpZL+JJ0Gk+BfR5R8ovXdYAhS3tWrFNXduQLQr6krMlncPFFM1j2FwfqD/iAt80yhIlCS2gr54uev+X9w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/ml-gsd/-/ml-gsd-14.1.0.tgz", + "integrity": "sha512-63kVpc2TceWTatbzZJHHTuR8wV1p9B9uWDHzOHqhfza2icfgz5rsONib5xWdUeN+9PCq4eosjky/aYRxjt8yow==", "license": "MIT", "dependencies": { "cheminfo-types": "^1.15.0", - "ml-peak-shape-generator": "^4.2.0", + "ml-peak-shape-generator": "^5.1.0", "ml-savitzky-golay-generalized": "^5.0.0", - "ml-spectra-fitting": "^6.0.1", + "ml-spectra-fitting": "^6.1.0", "ml-spectra-processing": "^14.28.1" } }, + "node_modules/ml-gsd/node_modules/ml-peak-shape-generator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ml-peak-shape-generator/-/ml-peak-shape-generator-5.1.0.tgz", + "integrity": "sha512-W2Vx/+R65zyr/B13p5TLyqwjTgIzwR0xtjRyQ2ZSxDHdVaXxb6m+ajyyaHJqGG5mYzeqi97nGvpklhz0jrsbrg==", + "license": "MIT", + "dependencies": { + "cheminfo-types": "^1.15.0" + } + }, "node_modules/ml-hash-table": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ml-hash-table/-/ml-hash-table-1.0.0.tgz", @@ -9441,18 +9459,27 @@ } }, "node_modules/ml-spectra-fitting": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ml-spectra-fitting/-/ml-spectra-fitting-6.0.1.tgz", - "integrity": "sha512-Izfi1ibuWsmnEivpoMK7GAEe16vHOL1V+4tIcL/YIsPuaUGvG1ydgmAOnkGxbYecM1QFgK1E3ZDFn0i2S9pzeA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ml-spectra-fitting/-/ml-spectra-fitting-6.1.0.tgz", + "integrity": "sha512-qCILP7xEU2sowZGoEalmxMdISM2LZSmwYXjxJYP4sqNRYRFA1Jxv84WnxwMTiNPNIa+wREhajmZamfuMm1blSQ==", "license": "MIT", "dependencies": { "cheminfo-types": "^1.7.2", "ml-direct": "^1.0.0", "ml-levenberg-marquardt": "^5.0.0", - "ml-peak-shape-generator": "^4.1.4", + "ml-peak-shape-generator": "^5.1.0", "ml-spectra-processing": "^14.2.0" } }, + "node_modules/ml-spectra-fitting/node_modules/ml-peak-shape-generator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ml-peak-shape-generator/-/ml-peak-shape-generator-5.1.0.tgz", + "integrity": "sha512-W2Vx/+R65zyr/B13p5TLyqwjTgIzwR0xtjRyQ2ZSxDHdVaXxb6m+ajyyaHJqGG5mYzeqi97nGvpklhz0jrsbrg==", + "license": "MIT", + "dependencies": { + "cheminfo-types": "^1.15.0" + } + }, "node_modules/ml-spectra-processing": { "version": "14.29.0", "resolved": "https://registry.npmjs.org/ml-spectra-processing/-/ml-spectra-processing-14.29.0.tgz", @@ -9575,9 +9602,9 @@ } }, "node_modules/nmr-processing": { - "version": "22.12.0", - "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-22.12.0.tgz", - "integrity": "sha512-sq8gh3g8mDadVFo+GhjqIdigOKxxg37kNHwOvLitqjAO5/50uBTTwNWm12ZBC87mYSMkGPGlvTSYhRSB7rccjA==", + "version": "22.13.0", + "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-22.13.0.tgz", + "integrity": "sha512-udS0fxLjJgojI382/6LZvBGLs+z+edoiULrqNjHYCQdFCgsEp6JYBNnHABY29myvXGN9ml17Clhc/qLaUJDe3w==", "license": "CC-BY-NC-SA-4.0", "dependencies": { "binary-search": "^1.3.6", @@ -9589,13 +9616,13 @@ "ml-airpls": "^2.2.0", "ml-convolution": "^2.0.0", "ml-direct": "^1.0.0", - "ml-gsd": "^14.0.0", + "ml-gsd": "^14.1.0", "ml-hclust": "^4.0.0", "ml-levenberg-marquardt": "^5.0.1", "ml-matrix": "^6.12.2", "ml-matrix-convolution": "^2.0.0", "ml-matrix-peaks-finder": "^2.0.0", - "ml-peak-shape-generator": "^4.2.0", + "ml-peak-shape-generator": "^5.1.0", "ml-regression-base": "^4.0.1", "ml-regression-exponential": "^3.0.2", "ml-regression-polynomial": "^4.0.0", @@ -9611,7 +9638,7 @@ "numeral": "^2.0.6", "openchemlib": "^9.22.0", "openchemlib-utils": "^8.15.0", - "spectrum-generator": "^8.2.0" + "spectrum-generator": "^8.2.1" } }, "node_modules/nmr-processing/node_modules/ml-matrix-convolution": { @@ -9633,6 +9660,15 @@ "ml-matrix-convolution": "^2.0.0" } }, + "node_modules/nmr-processing/node_modules/ml-peak-shape-generator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ml-peak-shape-generator/-/ml-peak-shape-generator-5.1.0.tgz", + "integrity": "sha512-W2Vx/+R65zyr/B13p5TLyqwjTgIzwR0xtjRyQ2ZSxDHdVaXxb6m+ajyyaHJqGG5mYzeqi97nGvpklhz0jrsbrg==", + "license": "MIT", + "dependencies": { + "cheminfo-types": "^1.15.0" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -11770,15 +11806,24 @@ "license": "CC0-1.0" }, "node_modules/spectrum-generator": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/spectrum-generator/-/spectrum-generator-8.2.0.tgz", - "integrity": "sha512-j//msAj0z7ubdhXPzFk7OHeHdZivLY+8DAccSPQESNy/tR6Nmf8wjVHhtlCzs7r46+uFmc6Zf72kpjwvu3UBmQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/spectrum-generator/-/spectrum-generator-8.2.1.tgz", + "integrity": "sha512-gbF55gpP6crd/MUjF7GiNbtpgKCKZig9yIxQkFQT34DYH0i3boKr001ANs5zs83QA3Buzu7LqcsEVB+Kt9vrIw==", "license": "MIT", "dependencies": { - "ml-peak-shape-generator": "^4.2.0", + "ml-peak-shape-generator": "^5.1.0", "ml-spectra-processing": "^14.28.1" } }, + "node_modules/spectrum-generator/node_modules/ml-peak-shape-generator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ml-peak-shape-generator/-/ml-peak-shape-generator-5.1.0.tgz", + "integrity": "sha512-W2Vx/+R65zyr/B13p5TLyqwjTgIzwR0xtjRyQ2ZSxDHdVaXxb6m+ajyyaHJqGG5mYzeqi97nGvpklhz0jrsbrg==", + "license": "MIT", + "dependencies": { + "cheminfo-types": "^1.15.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", diff --git a/package.json b/package.json index 79d66d625..a5d1c0b7a 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "ml-array-mean": "^2.0.0", "ml-array-median": "^2.0.0", "ml-conrec": "^6.0.0", - "ml-gsd": "^14.0.1", + "ml-gsd": "^14.1.0", "ml-signal-processing": "^2.2.1", "ml-spectra-processing": "^14.29.0", "ml-tree-similarity": "^2.2.0", diff --git a/src/component/1d/peaks/usePeakShapesPath.ts b/src/component/1d/peaks/usePeakShapesPath.ts index 303f81184..6c935f897 100644 --- a/src/component/1d/peaks/usePeakShapesPath.ts +++ b/src/component/1d/peaks/usePeakShapesPath.ts @@ -29,12 +29,13 @@ export function usePeakShapesPath(spectrum: Spectrum1D) { const frequency = spectrum.info.originFrequency; let pathSeries: DataXY | null = null; + switch (target) { case 'peakShape': { const { peak } = options; pathSeries = peakToXY(peak, { frequency, - nbPoints: 1024, + nbPoints: Math.ceil(width * 3), from: peak.x - (peak.width / frequency) * 9, to: peak.x + (peak.width / frequency) * 9, }); diff --git a/src/component/modal/EditPeakShapeModal.tsx b/src/component/modal/EditPeakShapeModal.tsx index 788cc0e10..41ebb3514 100644 --- a/src/component/modal/EditPeakShapeModal.tsx +++ b/src/component/modal/EditPeakShapeModal.tsx @@ -31,7 +31,8 @@ type Kind = | 'lorentzian' | 'pseudoVoigt' | 'generalizedLorentzian' - | 'lorentzianDispersive'; + | 'lorentzianDispersive' + | 'pseudoVoigtTCH'; function getKindDefaultValues(kind: Kind) { return { diff --git a/src/component/panels/PeaksPanel/PeaksPanel.tsx b/src/component/panels/PeaksPanel/PeaksPanel.tsx index 0b19199ee..fc7348cf1 100644 --- a/src/component/panels/PeaksPanel/PeaksPanel.tsx +++ b/src/component/panels/PeaksPanel/PeaksPanel.tsx @@ -111,11 +111,11 @@ function PeaksPanelInner(props: PeaksPanelInnerProps) { const filterPeaks = peaks.values.filter((peak) => isInRange(peak.x, { from, to }), ); - if (filterPeaks.length <= 4) { + if (filterPeaks.length <= 15) { dispatch({ type: 'OPTIMIZE_PEAKS', payload: { peaks: filterPeaks } }); } else { toaster.show({ - message: 'optimization can be done on no more than 4 peaks', + message: 'optimization can be done on no more than 15 peaks', intent: 'danger', }); } diff --git a/src/component/reducer/actions/PeaksActions.ts b/src/component/reducer/actions/PeaksActions.ts index 5c1e13a05..359e3fb28 100644 --- a/src/component/reducer/actions/PeaksActions.ts +++ b/src/component/reducer/actions/PeaksActions.ts @@ -102,7 +102,7 @@ function handleAddPeak(draft: Draft, action: AddPeakAction) { x: candidatePeak.x, y: candidatePeak.y, width: 1, - shape: defaultPeakShape, + shape: { ...defaultPeakShape }, }; spectrum.peaks.values.push(...mapPeaks([peak], spectrum)); } @@ -131,7 +131,7 @@ function handleAddPeaks(draft: Draft, action: AddPeaksAction) { x: peak.x, y: peak.y, width: 1, - shape: defaultPeakShape, + shape: { ...defaultPeakShape }, }; spectrum.peaks.values.push(newPeak); } diff --git a/src/data/data1d/Spectrum1D/peaks/optimizePeaks.ts b/src/data/data1d/Spectrum1D/peaks/optimizePeaks.ts index 486d13586..057b512c1 100644 --- a/src/data/data1d/Spectrum1D/peaks/optimizePeaks.ts +++ b/src/data/data1d/Spectrum1D/peaks/optimizePeaks.ts @@ -1,7 +1,7 @@ import type { Peak1D } from '@zakodium/nmr-types'; import type { Spectrum1D } from '@zakodium/nmrium-core'; import { xFindClosestIndex } from 'ml-spectra-processing'; -import { mapPeaks, xyPeaksOptimization } from 'nmr-processing'; +import { mapPeaks, xyPeaksOptimizationByStages } from 'nmr-processing'; interface OptimizePeaksOptions { from: number; @@ -31,10 +31,116 @@ export function optimizePeaks( x = x.subarray(fromIndex, ToIndex); re = re.subarray(fromIndex, ToIndex); - const newPeaks = xyPeaksOptimization({ x, y: re }, peaks, { + const newPeaks = xyPeaksOptimizationByStages({ x, y: re }, peaks, { frequency, - groupingFactor: 3, + groupingFactor: 10, + stages: [ + { + optimization: { + kind: 'lm', + options: { maxIterations: 20, errorTolerance: 1e-3 }, + }, + parameters: { + fwhm: { + optimize: true, + min: (peak: any) => (peak.shape?.fwhm ?? 0) / 2, + max: (peak: any) => (peak.shape?.fwhm ?? 0) * 2, + }, + mu: { optimize: false }, + x: { optimize: false }, + y: { + optimize: true, + init: (peak: any) => peak.y * 0.8, + }, + }, + }, + { + optimization: { + kind: 'lm', + options: { maxIterations: 20, errorTolerance: 5e-4 }, + }, + parameters: { + fwhm: { + optimize: true, + min: (peak: any) => (peak.shape?.fwhm ?? 0) / 2, + max: (peak: any) => (peak.shape?.fwhm ?? 0) * 2, + }, + mu: { optimize: false }, + x: { optimize: false }, + y: { + optimize: true, + }, + }, + }, + { + optimization: { + kind: 'lm', + options: { maxIterations: 20, errorTolerance: 1e-5 }, + }, + parameters: { + fwhm: { + optimize: true, + min: (peak: any) => (peak.shape?.fwhm ?? 0) / 2, + max: (peak: any) => (peak.shape?.fwhm ?? 0) * 2, + }, + mu: { optimize: true }, + x: { optimize: false }, + y: { + optimize: true, + }, + }, + }, + { + optimization: { + kind: 'lm', + options: { maxIterations: 20, errorTolerance: 5e-4 }, + }, + parameters: { + fwhm: { + optimize: true, + min: (peak: any) => (peak.shape?.fwhm ?? 0) / 3, + max: (peak: any) => (peak.shape?.fwhm ?? 0) * 3, + }, + mu: { optimize: true }, + x: { optimize: true }, + y: { optimize: true }, + }, + }, + { + optimization: { + kind: 'lm', + options: { maxIterations: 20, errorTolerance: 1e-4 }, + }, + parameters: { + fwhm: { + optimize: true, + min: (peak: any) => (peak.shape?.fwhm ?? 0) / 2, + max: (peak: any) => (peak.shape?.fwhm ?? 0) * 2, + }, + mu: { optimize: false }, + x: { optimize: true }, + y: { optimize: true }, + }, + }, + { + optimization: { + kind: 'lm', + options: { maxIterations: 20, errorTolerance: 1e-8 }, + }, + parameters: { + fwhm: { + optimize: true, + min: (peak: any) => (peak.shape?.fwhm ?? 0) / 2, + max: (peak: any) => (peak.shape?.fwhm ?? 0) * 2, + }, + mu: { optimize: true }, + x: { optimize: true }, + y: { optimize: true }, + }, + }, + ], }); + return mapPeaks(spectrum.peaks.values.concat(newPeaks), spectrum, { checkIsExisting: false, });