diff --git a/src/component/header/AutoPeakPickingOptionPanel.tsx b/src/component/header/AutoPeakPickingOptionPanel.tsx index cb076daadd..a83c204102 100644 --- a/src/component/header/AutoPeakPickingOptionPanel.tsx +++ b/src/component/header/AutoPeakPickingOptionPanel.tsx @@ -9,9 +9,10 @@ import Label from '../elements/Label.js'; import { NumberInput2Controller } from '../elements/NumberInput2Controller.js'; import { Select2Controller } from '../elements/Select2Controller.js'; import { useActiveNucleusTab } from '../hooks/useActiveNucleusTab.ts'; +import { useActiveSpectra } from '../hooks/useActiveSpectra.ts'; import { MIN_AREA_POINTS, - useCheckPointsNumberInWindowArea, + useCheckPointsNumberInSelectedSpectra, } from '../hooks/useCheckPointsNumberInWindowArea.js'; import { usePanelPreferences } from '../hooks/usePanelPreferences.ts'; @@ -43,7 +44,7 @@ interface AutoPeakPickingOptions { } const validationSchema = Yup.object().shape({ - maxNumberOfPeaks: Yup.number().min(0).required(), + maxNumberOfPeaks: Yup.number().min(1).required(), minMaxRatio: Yup.number().min(0).required(), noiseFactor: Yup.number().min(0).required(), direction: Yup.mixed() @@ -55,14 +56,16 @@ const INIT_VALUES: AutoPeakPickingOptions = { maxNumberOfPeaks: 50, minMaxRatio: 0.05, noiseFactor: 3, - direction: 'positive', + direction: 'both', }; export function AutoPeakPickingOptionPanel() { const dispatch = useDispatch(); - const pointsNumber = useCheckPointsNumberInWindowArea(); + const hasEnoughPoints = useCheckPointsNumberInSelectedSpectra(); const toaster = useToaster(); const nucleus = useActiveNucleusTab(); + const activeSpectra = useActiveSpectra(); + const { defaultPeakShape } = usePanelPreferences('peaks', nucleus); const { handleSubmit, @@ -75,7 +78,7 @@ export function AutoPeakPickingOptionPanel() { }); function handlePeakPicking(values: any) { - if (pointsNumber > MIN_AREA_POINTS) { + if (hasEnoughPoints) { dispatch({ type: 'AUTO_PEAK_PICKING', payload: { @@ -90,7 +93,6 @@ export function AutoPeakPickingOptionPanel() { }); } } - return ( ); diff --git a/src/component/header/RangesPickingOptionPanel.tsx b/src/component/header/RangesPickingOptionPanel.tsx index 001d93bc52..6bd78d4676 100644 --- a/src/component/header/RangesPickingOptionPanel.tsx +++ b/src/component/header/RangesPickingOptionPanel.tsx @@ -42,11 +42,11 @@ function RangesPickingOptionPanel() { resolver: yupResolver(validationSchema), mode: 'onChange', }); - const pointsNumber = useCheckPointsNumberInWindowArea(); + const hasEnoughPoints = useCheckPointsNumberInWindowArea(); const toaster = useToaster(); function handleRangesPicking(values: any) { - if (pointsNumber > MIN_AREA_POINTS) { + if (hasEnoughPoints) { dispatch({ type: 'AUTO_RANGES_DETECTION', payload: values, diff --git a/src/component/hooks/useCheckPointsNumberInWindowArea.ts b/src/component/hooks/useCheckPointsNumberInWindowArea.ts index 881774eaf1..68c3db38bf 100644 --- a/src/component/hooks/useCheckPointsNumberInWindowArea.ts +++ b/src/component/hooks/useCheckPointsNumberInWindowArea.ts @@ -1,12 +1,36 @@ -import type { Spectrum1D } from '@zakodium/nmrium-core'; import { xGetFromToIndex } from 'ml-spectra-processing'; +import type { Spectrum } from 'nmr-correlation'; +import { isSpectrum1D } from '../../data/data1d/Spectrum1D/isSpectrum1D.ts'; import { useChartData } from '../context/ChartContext.js'; -import useSpectrum from './useSpectrum.js'; +import { useSelectedSpectra } from './useSelectedSpectra.ts'; +import useSpectraByActiveNucleus from './useSpectraPerNucleus.ts'; +import useSpectrum from './useSpectrum.ts'; export const MIN_AREA_POINTS = 5; +function checkPointNumberInWindowArea( + spectra: Spectrum | Spectrum[], + from: number, + to: number, +) { + const spectraArray = Array.isArray(spectra) ? spectra : [spectra]; + const filteredSpectra = spectraArray.filter(isSpectrum1D); + + if (filteredSpectra.length === 0) { + return false; + } + + return filteredSpectra.every((spectrum) => { + const { fromIndex, toIndex } = xGetFromToIndex(spectrum.data.x, { + from, + to, + }); + return toIndex - fromIndex > MIN_AREA_POINTS; + }); +} + export function useCheckPointsNumberInWindowArea() { const state = useChartData(); const spectrum = useSpectrum(null); @@ -14,16 +38,20 @@ export function useCheckPointsNumberInWindowArea() { xDomain: [from, to], } = state; - if (spectrum) { - const { fromIndex, toIndex } = xGetFromToIndex( - (spectrum as Spectrum1D).data.x, - { - from, - to, - }, - ); - return toIndex - fromIndex; - } + return checkPointNumberInWindowArea(spectrum, from, to); +} + +export function useCheckPointsNumberInSelectedSpectra() { + const state = useChartData(); + const spectra = useSelectedSpectra(); + const spectraByActiveNucleus = useSpectraByActiveNucleus(); + const { + xDomain: [from, to], + } = state; - return 0; + return checkPointNumberInWindowArea( + spectra ?? spectraByActiveNucleus, + from, + to, + ); } diff --git a/src/component/hooks/useSelectedSpectra.ts b/src/component/hooks/useSelectedSpectra.ts new file mode 100644 index 0000000000..9b65321fe5 --- /dev/null +++ b/src/component/hooks/useSelectedSpectra.ts @@ -0,0 +1,26 @@ +import type { Spectrum } from '@zakodium/nmrium-core'; +import { useMemo } from 'react'; + +import { useChartData } from '../context/ChartContext.tsx'; + +import { useActiveSpectra } from './useActiveSpectra.ts'; + +export function useSelectedSpectra() { + const activeSpectrum = useActiveSpectra(); + const { data } = useChartData(); + + return useMemo(() => { + const spectra = []; + + if (!activeSpectrum || activeSpectrum?.length === 0) return null; + + for (const active of activeSpectrum) { + const spectrum = data?.[active.index]; + if (spectrum) { + spectra.push(spectrum); + } + } + + return spectra; + }, [activeSpectrum, data]); +} diff --git a/src/component/reducer/actions/PeaksActions.ts b/src/component/reducer/actions/PeaksActions.ts index 78c279711e..e6768aaedc 100644 --- a/src/component/reducer/actions/PeaksActions.ts +++ b/src/component/reducer/actions/PeaksActions.ts @@ -2,6 +2,7 @@ import type { Peak1D } from '@zakodium/nmr-types'; import type { PeaksViewState, RangesViewState, + Spectrum, ViewState, } from '@zakodium/nmrium-core'; import type { Draft } from 'immer'; @@ -21,7 +22,9 @@ import { defaultPeaksViewState } from '../../hooks/useActiveSpectrumPeaksViewSta import { getDefaultRangesViewState } from '../../hooks/useActiveSpectrumRangesViewState.js'; import type { FilterType } from '../../utility/filterType.js'; import { getClosePeak } from '../../utility/getClosePeak.js'; +import { getSpectraByNucleus } from '../../utility/getSpectraByNucleus.ts'; import type { State } from '../Reducer.js'; +import { getActiveSpectra } from '../helper/getActiveSpectra.ts'; import { getActiveSpectrum } from '../helper/getActiveSpectrum.js'; import getRange from '../helper/getRange.js'; import { getSpectrum } from '../helper/getSpectrum.js'; @@ -179,23 +182,40 @@ function handleAutoPeakPicking( ) { const { options, defaultPeakShape } = action.payload; - const spectrum = getSpectrum(draft); - if (!isSpectrum1D(spectrum)) return; + const activeSpectra = getActiveSpectra(draft); - draft.toolOptions.selectedTool = 'zoom'; - draft.toolOptions.selectedOptionPanel = null; + let spectra: Spectrum[] = []; + + if (!activeSpectra || activeSpectra.length === 0) { + spectra = getSpectraByNucleus(draft.view.spectra.activeTab, draft.data); + } else { + for (const activeSpectrum of activeSpectra) { + const spectrum = getSpectrum(draft, activeSpectrum.index); + if (spectrum) { + spectra.push(spectrum); + } + } + } const [from, to] = draft.xDomain; - const windowFromIndex = xFindClosestIndex(spectrum.data.x, from); - const windowToIndex = xFindClosestIndex(spectrum.data.x, to); - - const peaks = autoPeakPicking(spectrum, { - ...options, - windowFromIndex, - windowToIndex, - defaultPeakShape, - }); - spectrum.peaks.values = spectrum.peaks.values.concat(peaks); + + for (const spectrum of spectra) { + if (!isSpectrum1D(spectrum)) continue; + + const windowFromIndex = xFindClosestIndex(spectrum.data.x, from); + const windowToIndex = xFindClosestIndex(spectrum.data.x, to); + + const peaks = autoPeakPicking(spectrum, { + ...options, + windowFromIndex, + windowToIndex, + defaultPeakShape, + }); + spectrum.peaks.values = spectrum.peaks.values.concat(peaks); + } + + draft.toolOptions.selectedTool = 'zoom'; + draft.toolOptions.selectedOptionPanel = null; } //action diff --git a/src/component/toolbar/ToolTypes.ts b/src/component/toolbar/ToolTypes.ts index 7ff315c27a..d892d2e686 100644 --- a/src/component/toolbar/ToolTypes.ts +++ b/src/component/toolbar/ToolTypes.ts @@ -53,6 +53,9 @@ export const options: RecordOptions = { info: [{ key: 'isFt', value: true }], active: true, }, + { + active: false, + }, ], isToggle: true, }, diff --git a/test-e2e/panels/filters.test.ts b/test-e2e/panels/filters.test.ts index d98615b119..9289766694 100644 --- a/test-e2e/panels/filters.test.ts +++ b/test-e2e/panels/filters.test.ts @@ -162,7 +162,7 @@ test('process 13c spectrum with shortcuts', async ({ page }) => { await addPeaks(nmrium, { keyboard: true }); }); await test.step('Check peaks table', async () => { - await checkPeakNumber(nmrium, 16); + await checkPeakNumber(nmrium, 50); }); await test.step('Check filters panel', async () => { await expect(