diff --git a/package-lock.json b/package-lock.json index 42c2dd91b..52c844537 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "ml-tree-similarity": "^2.2.0", "multiplet-analysis": "^2.1.2", "nmr-correlation": "^2.3.3", - "nmr-load-save": "^2.0.4", + "nmr-load-save": "^2.1.0", "nmr-processing": "^14.0.3", "nmredata": "^0.9.11", "numeral": "^2.0.6", @@ -10021,9 +10021,9 @@ } }, "node_modules/nmr-load-save": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-2.0.4.tgz", - "integrity": "sha512-+HnY0Zlg9Texd5ZKjdPPE8mxCd7SstjwLXutMgw/NJQfHBG8swDB9OfITPhM+ibO60U6o9I3VoCoDqIE+gADrw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-2.1.0.tgz", + "integrity": "sha512-4QIjH/2W713uhLd4i417v1XuV+pknPDOJcYoeRiRHoFf4rev7/gx+QnYZ/f1g3PXzSvARFq6k70Wgwxckn2KBg==", "license": "MIT", "dependencies": { "@lukeed/uuid": "^2.0.1", diff --git a/package.json b/package.json index 06a8fcbcc..450896679 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "ml-tree-similarity": "^2.2.0", "multiplet-analysis": "^2.1.2", "nmr-correlation": "^2.3.3", - "nmr-load-save": "^2.0.4", + "nmr-load-save": "^2.1.0", "nmr-processing": "^14.0.3", "nmredata": "^0.9.11", "numeral": "^2.0.6", diff --git a/src/component/header/Header.tsx b/src/component/header/Header.tsx index dc03d3a4d..7a9cf2983 100644 --- a/src/component/header/Header.tsx +++ b/src/component/header/Header.tsx @@ -31,6 +31,8 @@ import { options } from '../toolbar/ToolTypes.js'; import { AutoPeakPickingOptionPanel } from './AutoPeakPickingOptionPanel.js'; import { HeaderWrapper } from './HeaderWrapper.js'; import RangesPickingOptionPanel from './RangesPickingOptionPanel.js'; +import { SimpleApodizationDimensionOneOptionsPanel } from './SimpleApodizationDimensionOneOptionsPanel.js'; +import { SimpleApodizationDimensionTwoOptionsPanel } from './SimpleApodizationDimensionTwoOptionsPanel.js'; import { SimpleApodizationOptionsPanel } from './SimpleApodizationOptionsPanel.js'; import { SimpleBaseLineCorrectionOptionsPanel } from './SimpleBaseLineCorrectionOptionsPanel.js'; import { SimplePhaseCorrectionOptionsPanel } from './SimplePhaseCorrectionOptionsPanel.js'; @@ -104,6 +106,10 @@ function HeaderInner(props: HeaderInnerProps) { switch (selectedOptionPanel) { case options.apodization.id: return ; + case options.apodizationDimension1.id: + return ; + case options.apodizationDimension2.id: + return ; case options.zeroFilling.id: return ; case options.phaseCorrection.id: diff --git a/src/component/header/SimpleApodizationDimensionOneOptionsPanel.tsx b/src/component/header/SimpleApodizationDimensionOneOptionsPanel.tsx new file mode 100644 index 000000000..22671435c --- /dev/null +++ b/src/component/header/SimpleApodizationDimensionOneOptionsPanel.tsx @@ -0,0 +1,54 @@ +import { memo, useCallback } from 'react'; + +import type { ExtractFilterEntry } from '../../data/types/common/ExtractFilterEntry.js'; +import { useDispatch } from '../context/DispatchContext.js'; +import { useFilter } from '../hooks/useFilter.js'; + +import { BaseSimpleApodizationOptionsPanel } from './BaseSimpleApodizationOptionsPanel.js'; + +interface ApodizationOptionsInnerPanelProps { + filter: ExtractFilterEntry<'apodizationDimension1'> | null; +} + +function ApodizationOptionsInnerPanel( + props: ApodizationOptionsInnerPanelProps, +) { + const dispatch = useDispatch(); + + const applyHandler = useCallback( + (data) => { + const { options } = data; + dispatch({ + type: 'APPLY_APODIZATION_DIMENSION_ONE_FILTER', + payload: { options }, + }); + }, + [dispatch], + ); + const changeHandler = useCallback( + (data) => { + const { livePreview, options } = data; + + dispatch({ + type: 'CALCULATE_APODIZATION_DIMENSION_ONE_FILTER', + payload: { livePreview, options: structuredClone(options) }, + }); + }, + [dispatch], + ); + + return ( + + ); +} + +const MemoizedApodizationPanel = memo(ApodizationOptionsInnerPanel); + +export function SimpleApodizationDimensionOneOptionsPanel() { + const filter = useFilter('apodizationDimension1'); + return ; +} diff --git a/src/component/header/SimpleApodizationDimensionTwoOptionsPanel.tsx b/src/component/header/SimpleApodizationDimensionTwoOptionsPanel.tsx new file mode 100644 index 000000000..d60922012 --- /dev/null +++ b/src/component/header/SimpleApodizationDimensionTwoOptionsPanel.tsx @@ -0,0 +1,54 @@ +import { memo, useCallback } from 'react'; + +import type { ExtractFilterEntry } from '../../data/types/common/ExtractFilterEntry.js'; +import { useDispatch } from '../context/DispatchContext.js'; +import { useFilter } from '../hooks/useFilter.js'; + +import { BaseSimpleApodizationOptionsPanel } from './BaseSimpleApodizationOptionsPanel.js'; + +interface ApodizationOptionsInnerPanelProps { + filter: ExtractFilterEntry<'apodizationDimension2'> | null; +} + +function ApodizationOptionsInnerPanel( + props: ApodizationOptionsInnerPanelProps, +) { + const dispatch = useDispatch(); + + const applyHandler = useCallback( + (data) => { + const { options } = data; + dispatch({ + type: 'APPLY_APODIZATION_DIMENSION_TWO_FILTER', + payload: { options }, + }); + }, + [dispatch], + ); + const changeHandler = useCallback( + (data) => { + const { livePreview, options } = data; + + dispatch({ + type: 'CALCULATE_APODIZATION_DIMENSION_TWO_FILTER', + payload: { livePreview, options: structuredClone(options) }, + }); + }, + [dispatch], + ); + + return ( + + ); +} + +const MemoizedApodizationPanel = memo(ApodizationOptionsInnerPanel); + +export function SimpleApodizationDimensionTwoOptionsPanel() { + const filter = useFilter('apodizationDimension2'); + return ; +} diff --git a/src/component/modal/setting/settings-tabs/ToolsTabContent.tsx b/src/component/modal/setting/settings-tabs/ToolsTabContent.tsx index 3739cb0c5..fc47d3f85 100644 --- a/src/component/modal/setting/settings-tabs/ToolsTabContent.tsx +++ b/src/component/modal/setting/settings-tabs/ToolsTabContent.tsx @@ -67,6 +67,14 @@ const LIST: ListItem[] = [ label: 'Apodization', name: 'apodization', }, + { + label: 'Apodization dimension one', + name: 'apodizationDimension1', + }, + { + label: 'Apodization dimension two', + name: 'apodizationDimension2', + }, { label: 'Zero filling', name: 'zeroFilling', diff --git a/src/component/panels/filtersPanel/Filters/ApodizationDimensionOneOptionsPanel.tsx b/src/component/panels/filtersPanel/Filters/ApodizationDimensionOneOptionsPanel.tsx new file mode 100644 index 000000000..a24bfa8dd --- /dev/null +++ b/src/component/panels/filtersPanel/Filters/ApodizationDimensionOneOptionsPanel.tsx @@ -0,0 +1,51 @@ +import { useCallback } from 'react'; + +import type { ExtractFilterEntry } from '../../../../data/types/common/ExtractFilterEntry.js'; +import { useDispatch } from '../../../context/DispatchContext.js'; + +import { BaseApodizationOptions } from './apodization/BaseApodizationOptions.js'; + +import type { BaseFilterOptionsPanelProps } from './index.js'; + +export default function ApodizationDimensionOneOptionsPanel( + props: BaseFilterOptionsPanelProps< + ExtractFilterEntry<'apodizationDimension1'> + >, +) { + const dispatch = useDispatch(); + + const { filter, enableEdit = true, onCancel, onConfirm } = props; + + const applyHandler = useCallback( + (data) => { + const { options } = data; + dispatch({ + type: 'APPLY_APODIZATION_DIMENSION_ONE_FILTER', + payload: { options }, + }); + }, + [dispatch], + ); + const changeHandler = useCallback( + (data) => { + const { livePreview, options } = data; + + dispatch({ + type: 'CALCULATE_APODIZATION_DIMENSION_ONE_FILTER', + payload: { livePreview, options: structuredClone(options) }, + }); + }, + [dispatch], + ); + + return ( + + ); +} diff --git a/src/component/panels/filtersPanel/Filters/ApodizationDimensionTwoOptionsPanel.tsx b/src/component/panels/filtersPanel/Filters/ApodizationDimensionTwoOptionsPanel.tsx new file mode 100644 index 000000000..9f3928b9e --- /dev/null +++ b/src/component/panels/filtersPanel/Filters/ApodizationDimensionTwoOptionsPanel.tsx @@ -0,0 +1,51 @@ +import { useCallback } from 'react'; + +import type { ExtractFilterEntry } from '../../../../data/types/common/ExtractFilterEntry.js'; +import { useDispatch } from '../../../context/DispatchContext.js'; + +import { BaseApodizationOptions } from './apodization/BaseApodizationOptions.js'; + +import type { BaseFilterOptionsPanelProps } from './index.js'; + +export default function ApodizationDimensionTwoOptionsPanel( + props: BaseFilterOptionsPanelProps< + ExtractFilterEntry<'apodizationDimension2'> + >, +) { + const dispatch = useDispatch(); + + const { filter, enableEdit = true, onCancel, onConfirm } = props; + + const applyHandler = useCallback( + (data) => { + const { options } = data; + dispatch({ + type: 'APPLY_APODIZATION_DIMENSION_TWO_FILTER', + payload: { options }, + }); + }, + [dispatch], + ); + const changeHandler = useCallback( + (data) => { + const { livePreview, options } = data; + + dispatch({ + type: 'CALCULATE_APODIZATION_DIMENSION_TWO_FILTER', + payload: { livePreview, options: structuredClone(options) }, + }); + }, + [dispatch], + ); + + return ( + + ); +} diff --git a/src/component/panels/filtersPanel/Filters/index.tsx b/src/component/panels/filtersPanel/Filters/index.tsx index d2fd5cff4..368d03549 100644 --- a/src/component/panels/filtersPanel/Filters/index.tsx +++ b/src/component/panels/filtersPanel/Filters/index.tsx @@ -3,6 +3,8 @@ import { Filters1D, Filters2D } from 'nmr-processing'; import type { LabelStyle } from '../../../elements/Label.js'; +import ApodizationDimensionOneOptionsPanel from './ApodizationDimensionOneOptionsPanel.js'; +import ApodizationDimensionTwoOptionsPanel from './ApodizationDimensionTwoOptionsPanel.js'; import ApodizationOptionsPanel from './ApodizationOptionsPanel.js'; import BaseLineCorrectionOptionsPanel from './BaseLineCorrectionOptionsPanel.js'; import ExclusionZonesOptionsPanel from './ExclusionZonesOptionsPanel.js'; @@ -20,10 +22,18 @@ const { exclusionZones, } = Filters1D; -const { shift2DX, shift2DY, phaseCorrectionTwoDimensions } = Filters2D; +const { + shift2DX, + shift2DY, + phaseCorrectionTwoDimensions, + apodizationDimension1, + apodizationDimension2, +} = Filters2D; export const filterOptionPanels = { [apodization.name]: ApodizationOptionsPanel, + [apodizationDimension1.name]: ApodizationDimensionOneOptionsPanel, + [apodizationDimension2.name]: ApodizationDimensionTwoOptionsPanel, [phaseCorrection.name]: PhaseCorrectionOptionsPanel, [zeroFilling.name]: ZeroFillingOptionsPanel, [phaseCorrectionTwoDimensions.name]: PhaseCorrectionTwoDimensionsOptionsPanel, diff --git a/src/component/reducer/Reducer.ts b/src/component/reducer/Reducer.ts index 29b3785d0..efb932087 100644 --- a/src/component/reducer/Reducer.ts +++ b/src/component/reducer/Reducer.ts @@ -5,10 +5,7 @@ import { produce, original } from 'immer'; import type { CorrelationData } from 'nmr-correlation'; import { buildCorrelationData } from 'nmr-correlation'; import type { Spectrum, ViewState } from 'nmr-load-save'; -import type { - BaselineCorrectionZone, - Apodization1DOptions, -} from 'nmr-processing'; +import type { BaselineCorrectionZone } from 'nmr-processing'; import type { Reducer } from 'react'; import type { StateMoleculeExtended } from '../../data/molecules/Molecule.js'; @@ -169,7 +166,6 @@ export const getInitialState = (): State => ({ zones: [], livePreview: true, }, - apodizationOptions: {} as Apodization1DOptions, twoDimensionPhaseCorrection: { activeTraceDirection: 'horizontal', addTracesToBothDirections: true, @@ -347,7 +343,6 @@ export interface State { options: any; livePreview: boolean; }; - apodizationOptions: Apodization1DOptions; /** * pivot point for manual phase correction * @default {value:0,index:0} @@ -463,8 +458,28 @@ function innerSpectrumReducer(draft: Draft, action: Action) { return FiltersActions.handleShiftSpectrumAlongXAxis(draft, action); case 'APPLY_APODIZATION_FILTER': return FiltersActions.handleApplyApodizationFilter(draft, action); + case 'APPLY_APODIZATION_DIMENSION_ONE_FILTER': + return FiltersActions.handleApplyApodizationDimensionOneFilter( + draft, + action, + ); + case 'APPLY_APODIZATION_DIMENSION_TWO_FILTER': + return FiltersActions.handleApplyApodizationDimensionTwoFilter( + draft, + action, + ); case 'CALCULATE_APODIZATION_FILTER': return FiltersActions.handleCalculateApodizationFilter(draft, action); + case 'CALCULATE_APODIZATION_DIMENSION_ONE_FILTER': + return FiltersActions.handleCalculateApodizationDimensionOneFilter( + draft, + action, + ); + case 'CALCULATE_APODIZATION_DIMENSION_TWO_FILTER': + return FiltersActions.handleCalculateApodizationDimensionTwoFilter( + draft, + action, + ); case 'APPLY_ZERO_FILLING_FILTER': return FiltersActions.handleApplyZeroFillingFilter(draft, action); case 'CALCULATE_ZERO_FILLING_FILTER': diff --git a/src/component/reducer/actions/FiltersActions.ts b/src/component/reducer/actions/FiltersActions.ts index 84f183528..fe8c41cc1 100644 --- a/src/component/reducer/actions/FiltersActions.ts +++ b/src/component/reducer/actions/FiltersActions.ts @@ -3,14 +3,18 @@ import type { NmrData1D, NmrData2DFt } from 'cheminfo-types'; import type { Draft } from 'immer'; import { current } from 'immer'; import { xFindClosestIndex } from 'ml-spectra-processing'; -import type { ActiveSpectrum, Spectrum, Spectrum1D } from 'nmr-load-save'; +import type { + ActiveSpectrum, + Spectrum, + Spectrum1D, + Spectrum2D, +} from 'nmr-load-save'; import { getBaselineZonesByDietrich, Filters1DManager, Filters2DManager, Filters1D, Filters2D, - default1DApodization, } from 'nmr-processing'; import type { BaselineCorrectionOptions, @@ -90,10 +94,26 @@ type ApodizationFilterAction = ActionType< 'APPLY_APODIZATION_FILTER', { options: Apodization1DOptions } >; +type ApodizationDimensionOneFilterAction = ActionType< + 'APPLY_APODIZATION_DIMENSION_ONE_FILTER', + { options: Apodization1DOptions } +>; +type ApodizationDimensionTwoFilterAction = ActionType< + 'APPLY_APODIZATION_DIMENSION_TWO_FILTER', + { options: Apodization1DOptions } +>; type ApodizationFilterLiveAction = ActionType< 'CALCULATE_APODIZATION_FILTER', { options: Apodization1DOptions; livePreview: boolean } >; +type ApodizationDimensionOneFilterLiveAction = ActionType< + 'CALCULATE_APODIZATION_DIMENSION_ONE_FILTER', + { options: Apodization1DOptions; livePreview: boolean } +>; +type ApodizationDimensionTwoFilterLiveAction = ActionType< + 'CALCULATE_APODIZATION_DIMENSION_TWO_FILTER', + { options: Apodization1DOptions; livePreview: boolean } +>; type ZeroFillingFilterAction = ActionType< 'APPLY_ZERO_FILLING_FILTER', { options: { nbPoints: number } } @@ -180,7 +200,11 @@ type SetTwoDimensionPhaseCorrectionPivotPoint = ActionType< export type FiltersActions = | ShiftSpectrumAction | ApodizationFilterAction + | ApodizationDimensionOneFilterAction + | ApodizationDimensionTwoFilterAction | ApodizationFilterLiveAction + | ApodizationDimensionOneFilterLiveAction + | ApodizationDimensionTwoFilterLiveAction | ZeroFillingFilterAction | ZeroFillingFilterLiveAction | ManualPhaseCorrectionFilterAction @@ -549,14 +573,8 @@ function beforeRollback(draft: Draft, filterKey) { } } function afterRollback(draft: Draft, filterKey) { - // const activeSpectrum = getActiveSpectrum(draft); - switch (filterKey) { - case apodization.name: { - draft.toolOptions.data.apodizationOptions = - structuredClone(default1DApodization); - break; - } + //specify the filters here default: break; } @@ -600,17 +618,6 @@ function disableLivePreview(draft: Draft, id: string) { if (baselineCorrection.name !== id) { setDomain(draft); } - - // reset default options - switch (id) { - case apodization.name: { - draft.toolOptions.data.apodizationOptions = - structuredClone(default1DApodization); - break; - } - default: - break; - } } function isOneDimensionShift( @@ -750,7 +757,6 @@ function handleCalculateApodizationFilter( const _data = { data: { x, re, im }, info } as Spectrum1D; - draft.toolOptions.data.apodizationOptions = options; apodization.apply(_data, options); const { im: newIm, re: newRe } = _data.data; const datum = draft.data[index]; @@ -764,6 +770,72 @@ function handleCalculateApodizationFilter( disableLivePreview(draft, apodization.name); } } +//action +function handleCalculateApodizationDimensionOneFilter( + draft: Draft, + action: ApodizationDimensionOneFilterLiveAction, +) { + const activeSpectrum = getActiveSpectrum(draft); + + if (!activeSpectrum || !draft.tempData) { + return; + } + + const index = activeSpectrum.index; + const { livePreview, options } = action.payload; + if (livePreview) { + const { data, info } = current(draft).tempData[index]; + + const _data = structuredClone({ + data, + info, + }) as Spectrum2D; + + Filters2D.apodizationDimension1.apply(_data, options); + + const datum = draft.data[index]; + + if (!isSpectrum2D(datum)) { + return; + } + datum.data = _data.data; + } else { + disableLivePreview(draft, Filters2D.apodizationDimension1.name); + } +} +//action +function handleCalculateApodizationDimensionTwoFilter( + draft: Draft, + action: ApodizationDimensionTwoFilterLiveAction, +) { + const activeSpectrum = getActiveSpectrum(draft); + + if (!activeSpectrum || !draft.tempData) { + return; + } + + const index = activeSpectrum.index; + const { livePreview, options } = action.payload; + if (livePreview) { + const { data, info } = current(draft).tempData[index]; + + const _data = structuredClone({ + data, + info, + }) as Spectrum2D; + + Filters2D.apodizationDimension2.apply(_data, options); + + const datum = draft.data[index]; + + if (!isSpectrum2D(datum)) { + return; + } + datum.data = _data.data; + } else { + disableLivePreview(draft, Filters2D.apodizationDimension2.name); + } +} //action function handleApplyApodizationFilter( @@ -788,6 +860,52 @@ function handleApplyApodizationFilter( updateView(draft, apodization.domainUpdateRules); } +//action +function handleApplyApodizationDimensionOneFilter( + draft: Draft, + action: ApodizationDimensionOneFilterAction, +) { + const activeSpectrum = getActiveSpectrum(draft); + + if (!activeSpectrum || !draft.tempData) { + return; + } + + const index = activeSpectrum.index; + + Filters2DManager.applyFilters(draft.tempData[index], [ + { + name: 'apodizationDimension1', + value: action.payload.options, + }, + ]); + draft.data[index] = draft.tempData[index]; + + updateView(draft, apodization.domainUpdateRules); +} +//action +function handleApplyApodizationDimensionTwoFilter( + draft: Draft, + action: ApodizationDimensionTwoFilterAction, +) { + const activeSpectrum = getActiveSpectrum(draft); + + if (!activeSpectrum || !draft.tempData) { + return; + } + + const index = activeSpectrum.index; + + Filters2DManager.applyFilters(draft.tempData[index], [ + { + name: 'apodizationDimension2', + value: action.payload.options, + }, + ]); + draft.data[index] = draft.tempData[index]; + + updateView(draft, apodization.domainUpdateRules); +} //action function handleApplyFFTFilter(draft: Draft) { @@ -1589,6 +1707,8 @@ export { handleShiftSpectrumAlongXAxis, handleApplyZeroFillingFilter, handleApplyApodizationFilter, + handleApplyApodizationDimensionOneFilter, + handleApplyApodizationDimensionTwoFilter, handleApplyFFTFilter, handleApplyFFtDimension1Filter, handleApplyFFtDimension2Filter, @@ -1600,6 +1720,8 @@ export { calculateBaseLineCorrection, handleCalculateBaseLineCorrection, handleCalculateApodizationFilter, + handleCalculateApodizationDimensionOneFilter, + handleCalculateApodizationDimensionTwoFilter, handleCalculateZeroFillingFilter, handleEnableFilter, handleDeleteFilter, diff --git a/src/component/toolbar/ToolBar.tsx b/src/component/toolbar/ToolBar.tsx index 1cc965752..da82c7aed 100644 --- a/src/component/toolbar/ToolBar.tsx +++ b/src/component/toolbar/ToolBar.tsx @@ -305,6 +305,20 @@ export default function ToolBar() { }, icon: , }, + { + id: 'apodizationDimension1', + tooltip: { + title: options.apodizationDimension1.label, + }, + icon: , + }, + { + id: 'apodizationDimension2', + tooltip: { + title: options.apodizationDimension2.label, + }, + icon: , + }, { id: 'zeroFilling', tooltip: { diff --git a/src/component/toolbar/ToolTypes.ts b/src/component/toolbar/ToolTypes.ts index aa5207699..53af6461d 100644 --- a/src/component/toolbar/ToolTypes.ts +++ b/src/component/toolbar/ToolTypes.ts @@ -1,6 +1,6 @@ import type { NMRiumToolBarPreferences } from 'nmr-load-save'; import type { Info1D, Info2D } from 'nmr-processing'; -import { Filters1D } from 'nmr-processing'; +import { Filters1D, Filters2D } from 'nmr-processing'; import type { DisplayerMode } from '../reducer/Reducer.js'; @@ -417,4 +417,34 @@ export const options: RecordOptions = { isFilter: false, isToggle: false, }, + apodizationDimension1: { + id: Filters2D.apodizationDimension1.name, + label: Filters2D.apodizationDimension1.label, + hasOptionPanel: true, + isFilter: true, + mode: '2D', + spectraOptions: [ + { + info: [{ key: 'isFid', value: true }], + active: true, + }, + ], + isToggle: true, + isExperimental: true, + }, + apodizationDimension2: { + id: Filters2D.apodizationDimension2.name, + label: Filters2D.apodizationDimension2.label, + hasOptionPanel: true, + isFilter: true, + mode: '2D', + spectraOptions: [ + { + info: [{ key: 'isFt', value: true }], + active: true, + }, + ], + isToggle: true, + isExperimental: true, + }, };