From dae6bae55cbe6cb17ceb2fc746bf61f1b929af3b Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Wed, 30 Sep 2026 13:10:25 +0200 Subject: [PATCH] fix(nmr-cli): build as ESM to restore nmr-processing exports nmr-processing v22 is ESM-only, but the CLI compiled to CommonJS. Under require() only a handful of its exports resolved, so mapPeaks was undefined and parse-spectra failed with "(0, nmr_processing_1.mapPeaks) is not a function". - set "type": "module" so tsc emits ESM under NodeNext - add explicit .js extensions to all relative imports - copy package-lock.json and use npm ci in the Docker build for reproducible dependency resolution --- app/scripts/nmr-cli/.dockerignore | 1 - app/scripts/nmr-cli/Dockerfile | 4 +- app/scripts/nmr-cli/package-lock.json | 22 +- app/scripts/nmr-cli/package.json | 3 +- app/scripts/nmr-cli/src/correlation.ts | 4 +- app/scripts/nmr-cli/src/index.ts | 61 +-- .../data/data1D/convertDataToFloat64Array.ts | 6 +- .../src/parse/data/data1D/detectRanges.ts | 255 +++++----- .../src/parse/data/data1D/initSumOptions.ts | 47 +- .../src/parse/data/data1D/initiateDatum1D.ts | 66 ++- .../parse/data/data1D/initiateIntegrals.ts | 14 +- .../src/parse/data/data1D/initiatePeaks.ts | 12 +- .../src/parse/data/data1D/initiateRanges.ts | 14 +- .../src/parse/data/data2d/detectZones.ts | 37 +- .../parse/data/data2d/getDetectionZones.ts | 96 ++-- .../src/parse/data/data2d/initiateDatum2D.ts | 53 +-- .../src/parse/data/data2d/initiateZones.ts | 14 +- .../src/parse/data/data2d/isSpectrum2D.ts | 26 +- .../nmr-cli/src/parse/data/initiateFilters.ts | 6 +- .../nmr-cli/src/parse/prase-spectra.ts | 262 +++++----- .../src/parse/type/MoleculeExtended.ts | 17 +- .../nmr-cli/src/parse/utility/getAtom.ts | 2 +- .../nmr-cli/src/parse/utility/isProton.ts | 2 +- app/scripts/nmr-cli/src/peaks-to-nmrium.ts | 4 +- .../nmr-cli/src/prediction/engines/base.ts | 70 +-- .../nmr-cli/src/prediction/engines/index.ts | 8 +- .../engines/nmrdb/core/checkFromTo.ts | 148 +++--- .../engines/nmrdb/core/generateName.ts | 12 +- .../engines/nmrdb/core/generated1DSpectrum.ts | 125 ++--- .../engines/nmrdb/core/generated2DSpectrum.ts | 106 +++-- .../engines/nmrdb/core/getSpectralWidth.ts | 43 +- .../prediction/engines/nmrdb/core/mapZones.ts | 52 +- .../prediction/engines/nmrdb/nmrdb.engine.ts | 365 +++++++------- .../nmrshift/core/extractInfoFromSpectra.ts | 24 +- .../nmrshift/core/getNucleusFromSpectra.ts | 20 +- .../engines/nmrshift/core/spectraTypeMap.ts | 11 +- .../engines/nmrshift/nmrshift.engine.ts | 447 +++++++++--------- .../src/prediction/engines/registry.ts | 86 ++-- app/scripts/nmr-cli/src/prediction/index.ts | 379 ++++++++------- app/scripts/nmr-cli/src/publication-string.ts | 12 +- .../nmr-cli/src/utilities/adjustAlpha.ts | 12 +- app/scripts/nmr-cli/src/utilities/isProton.ts | 2 +- 42 files changed, 1503 insertions(+), 1447 deletions(-) diff --git a/app/scripts/nmr-cli/.dockerignore b/app/scripts/nmr-cli/.dockerignore index e17de89..bdcf593 100644 --- a/app/scripts/nmr-cli/.dockerignore +++ b/app/scripts/nmr-cli/.dockerignore @@ -1,4 +1,3 @@ -package-lock.json node_modules/ build/ diff --git a/app/scripts/nmr-cli/Dockerfile b/app/scripts/nmr-cli/Dockerfile index f7877d5..e6dfe1e 100644 --- a/app/scripts/nmr-cli/Dockerfile +++ b/app/scripts/nmr-cli/Dockerfile @@ -32,9 +32,9 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -COPY package.json ./ +COPY package.json package-lock.json ./ -RUN npm install +RUN npm ci COPY . ./ diff --git a/app/scripts/nmr-cli/package-lock.json b/app/scripts/nmr-cli/package-lock.json index 91f5f24..1693735 100644 --- a/app/scripts/nmr-cli/package-lock.json +++ b/app/scripts/nmr-cli/package-lock.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@types/lodash.merge": "^4.6.9", - "@types/node": "^26.2.0", + "@types/node": "^26.6.3", "@types/yargs": "^17.0.35", "ts-node": "^10.9.2", "typescript": "^5.9.3" @@ -129,14 +129,13 @@ } }, "node_modules/@types/node": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", - "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/yargs": { @@ -1336,8 +1335,7 @@ "version": "9.25.0", "resolved": "https://registry.npmjs.org/openchemlib/-/openchemlib-9.25.0.tgz", "integrity": "sha512-FGTaZLJRTGXNC7khx8QvX/EiQBHpH1ncUbT7YXDJhw/Y/aDUKe5WrUIqTguMEtSs3GUHcRUjNUhfkpxulx2UXw==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/openchemlib-utils": { "version": "8.18.0", @@ -1510,7 +1508,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1526,9 +1523,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -1622,7 +1619,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/app/scripts/nmr-cli/package.json b/app/scripts/nmr-cli/package.json index 9180a20..8d08232 100644 --- a/app/scripts/nmr-cli/package.json +++ b/app/scripts/nmr-cli/package.json @@ -2,6 +2,7 @@ "name": "nmr-cli", "version": "1.0.0", "description": "", + "type": "module", "main": "./build/index.js", "scripts": { "build": "tsc", @@ -33,7 +34,7 @@ }, "devDependencies": { "@types/lodash.merge": "^4.6.9", - "@types/node": "^26.2.0", + "@types/node": "^26.6.3", "@types/yargs": "^17.0.35", "ts-node": "^10.9.2", "typescript": "^5.9.3" diff --git a/app/scripts/nmr-cli/src/correlation.ts b/app/scripts/nmr-cli/src/correlation.ts index 0c8382b..b12b8b0 100644 --- a/app/scripts/nmr-cli/src/correlation.ts +++ b/app/scripts/nmr-cli/src/correlation.ts @@ -8,8 +8,8 @@ import { loadFileCollection, parsingOptions, processSpectra, -} from './parse/prase-spectra' -import { isSpectrum2D } from './parse/data/data2d/isSpectrum2D' +} from './parse/prase-spectra.js' +import { isSpectrum2D } from './parse/data/data2d/isSpectrum2D.js' // Default tolerances confirmed by vcnainala on issue #66 const DEFAULT_TOLERANCE_H = 0.02 diff --git a/app/scripts/nmr-cli/src/index.ts b/app/scripts/nmr-cli/src/index.ts index 7ca4b70..143c1dd 100755 --- a/app/scripts/nmr-cli/src/index.ts +++ b/app/scripts/nmr-cli/src/index.ts @@ -1,14 +1,13 @@ #!/usr/bin/env node import yargs, { type Argv, type CommandModule, type Options } from 'yargs' -import { parseSpectra } from './parse/prase-spectra' -import { generateSpectrumFromPublicationString } from './publication-string' -import { generateNMRiumFromPeaks } from './peaks-to-nmrium' -import type { PeaksToNMRiumInput } from './peaks-to-nmrium' -import { generateCorrelationData } from './correlation' +import { parseSpectra } from './parse/prase-spectra.js' +import { generateSpectrumFromPublicationString } from './publication-string.js' +import { generateNMRiumFromPeaks } from './peaks-to-nmrium.js' +import type { PeaksToNMRiumInput } from './peaks-to-nmrium.js' +import { generateCorrelationData } from './correlation.js' import { hideBin } from 'yargs/helpers' -import { parsePredictionCommand } from './prediction' -import { readFileSync } from 'fs' -import { IncludeData } from '@zakodium/nmrium-core' +import { parsePredictionCommand } from './prediction/index.js' +import { readFileSync } from 'node:fs' const usageMessage = ` Usage: nmr-cli [options] @@ -103,59 +102,58 @@ Examples: ` export interface FileOptionsArgs { - /** + /** * -u, --url * File URL to load remote spectra or data. */ - u?: string; + u?: string - /** + /** * -dir, --dir-path * Local directory path for file input or output. */ - dir?: string; + dir?: string - /** + /** * -s, --capture-snapshot * Capture a visual snapshot of the current state or spectrum. */ - s?: boolean; + s?: boolean - /** + /** * -p, --auto-processing * Automatically process spectrum from FID to FT spectra. * Mandatory when automatic detection (`--auto-detection`) is enabled. */ - p?: boolean; + p?: boolean - /** + /** * -d, --auto-detection * Perform automatic ranges and zones detection. */ - d?: boolean; + d?: boolean /** - * -o, --output + * -o, --output * Output file path */ - o?: string; + o?: string /** - * -r, --raw-data + * -r, --raw-data * Include raw data in the output, defaults to dataSource */ - r?: boolean; + r?: boolean /** * --include * Only include files matching these pattern(s) when reading a directory (file-collection's filter.include). */ - include?: string[]; + include?: string[] /** * --exclude * Exclude files matching these pattern(s) when reading a directory (file-collection's filter.exclude). */ - exclude?: string[]; - + exclude?: string[] } // Define options for parsing a spectra file @@ -201,12 +199,14 @@ const fileOptions: { [key in keyof FileOptionsArgs]: Options } = { include: { type: 'array', string: true, - description: 'Only include files matching pattern(s) when reading a directory (glob/regex string)', + description: + 'Only include files matching pattern(s) when reading a directory (glob/regex string)', }, exclude: { type: 'array', string: true, - description: 'Exclude files matching pattern(s) when reading a directory (glob/regex string)', + description: + 'Exclude files matching pattern(s) when reading a directory (glob/regex string)', }, } as const @@ -252,7 +252,7 @@ const peaksToNMRiumCommand: CommandModule = { } catch (error) { console.error( 'Error:', - error instanceof Error ? error.message : String(error), + error instanceof Error ? error.message : String(error) ) process.exit(1) } @@ -262,7 +262,8 @@ const peaksToNMRiumCommand: CommandModule = { // Define the correlation command const correlationCommand: CommandModule = { command: ['correlation', 'corr'], - describe: 'Build correlation data from NMR spectra fetched from a URL or a local directory', + describe: + 'Build correlation data from NMR spectra fetched from a URL or a local directory', builder: yargs => { return yargs .options({ @@ -312,7 +313,7 @@ const correlationCommand: CommandModule = { } catch (error) { console.error( 'Error:', - error instanceof Error ? error.message : String(error), + error instanceof Error ? error.message : String(error) ) process.exit(1) } diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/convertDataToFloat64Array.ts b/app/scripts/nmr-cli/src/parse/data/data1D/convertDataToFloat64Array.ts index b0c39d4..dcbe532 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/convertDataToFloat64Array.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/convertDataToFloat64Array.ts @@ -1,7 +1,7 @@ -import type { NmrData1D } from 'cheminfo-types'; +import type { NmrData1D } from 'cheminfo-types' function convert(value: Float64Array | number[] = []): Float64Array { - return !ArrayBuffer.isView(value) && value ? Float64Array.from(value) : value; + return !ArrayBuffer.isView(value) && value ? Float64Array.from(value) : value } export function convertDataToFloat64Array(data: NmrData1D): NmrData1D { @@ -9,5 +9,5 @@ export function convertDataToFloat64Array(data: NmrData1D): NmrData1D { x: convert(data.x), re: convert(data.re), im: convert(data?.im), - }; + } } diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/detectRanges.ts b/app/scripts/nmr-cli/src/parse/data/data1D/detectRanges.ts index dd8ddd7..b632abc 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/detectRanges.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/detectRanges.ts @@ -1,145 +1,142 @@ -import { xFindClosestIndex } from "ml-spectra-processing"; -import { isProton } from "../../utility/isProton"; -import { Spectrum1D } from "@zakodium/nmrium-core"; -import { mapRanges, OptionsXYAutoPeaksPicking, updateRangesRelativeValues, xyAutoRangesPicking } from "nmr-processing"; - +import { xFindClosestIndex } from 'ml-spectra-processing' +import { isProton } from '../../utility/isProton.js' +import { Spectrum1D } from '@zakodium/nmrium-core' +import { + mapRanges, + OptionsXYAutoPeaksPicking, + updateRangesRelativeValues, + xyAutoRangesPicking, +} from 'nmr-processing' //TODO expose OptionsPeaksToRanges from nmr-processing interface OptionsPeaksToRanges { - /** - * Number of hydrogens or some number to normalize the integration data. If it's zero return the absolute integration value - * @default 100 - */ - integrationSum?: number; - /** - * if it is true, it will join any overlaped ranges. - * @default true - */ - joinOverlapRanges?: boolean; - /** - * If exits it remove all the signals with integration < clean value - * @default 0.4 - */ - clean?: number; - /** - * If true, the Janalyzer function is run over signals to compile the patterns. - * @default true - */ - compile?: boolean; - /** - * option to chose between approx area with peaks or the sum of the points of given range ('sum', 'peaks') - * @default 'sum' - */ - integralType?: string; - /** - * Observed frequency - * @default 400 - */ - frequency?: number; - /** - * distance limit to clustering peaks. - * @default 16 - */ - frequencyCluster?: number; - /** - * If true, it will keep the peaks for each signal - */ - keepPeaks?: boolean; - /** - * Nucleus - * @default '1H' - */ - nucleus?: string; - /** - * ratio of heights between the extreme peaks - * @default 1.5 - */ - symRatio?: number; + /** + * Number of hydrogens or some number to normalize the integration data. If it's zero return the absolute integration value + * @default 100 + */ + integrationSum?: number + /** + * if it is true, it will join any overlaped ranges. + * @default true + */ + joinOverlapRanges?: boolean + /** + * If exits it remove all the signals with integration < clean value + * @default 0.4 + */ + clean?: number + /** + * If true, the Janalyzer function is run over signals to compile the patterns. + * @default true + */ + compile?: boolean + /** + * option to chose between approx area with peaks or the sum of the points of given range ('sum', 'peaks') + * @default 'sum' + */ + integralType?: string + /** + * Observed frequency + * @default 400 + */ + frequency?: number + /** + * distance limit to clustering peaks. + * @default 16 + */ + frequencyCluster?: number + /** + * If true, it will keep the peaks for each signal + */ + keepPeaks?: boolean + /** + * Nucleus + * @default '1H' + */ + nucleus?: string + /** + * ratio of heights between the extreme peaks + * @default 1.5 + */ + symRatio?: number } - interface AutoDetectOptions { - from?: number; - to?: number; - minMaxRatio?: number; - lookNegative?: number; + from?: number + to?: number + minMaxRatio?: number + lookNegative?: number } - - export function detectRanges( - spectrum: Spectrum1D, - options: AutoDetectOptions = {}, + spectrum: Spectrum1D, + options: AutoDetectOptions = {} ) { - - - const { from, to, minMaxRatio = 0.05, lookNegative = false } = options - const { info: { nucleus, solvent, originFrequency }, data } = spectrum; - let { x, re } = data - const windowFromIndex = from ? xFindClosestIndex(x, from) : undefined; - const windowToIndex = to ? xFindClosestIndex(x, to) : undefined; - - const isProtonic = isProton(nucleus); - - const peakPickingOptions: OptionsXYAutoPeaksPicking = { - ...defaultPeakPickingOptions, - smoothY: undefined, - sensitivity: 100, - broadWidth: 0.05, - thresholdFactor: 8, - minMaxRatio, - direction: lookNegative ? 'both' : 'positive', - frequency: originFrequency, - sgOptions: undefined, - - }; - - const rangesOptions: OptionsPeaksToRanges = { - nucleus, - compile: isProtonic, - frequency: originFrequency, - integrationSum: isProtonic ? spectrum.ranges.options.sum : 100, - frequencyCluster: isProtonic ? 16 : 0, - clean: 0.5, - keepPeaks: true, - joinOverlapRanges: isProtonic, - }; - - if (windowFromIndex !== undefined && windowToIndex !== undefined) { - x = x.slice(windowFromIndex, windowToIndex); - re = re.slice(windowFromIndex, windowToIndex); + const { from, to, minMaxRatio = 0.05, lookNegative = false } = options + const { + info: { nucleus, solvent, originFrequency }, + data, + } = spectrum + let { x, re } = data + const windowFromIndex = from ? xFindClosestIndex(x, from) : undefined + const windowToIndex = to ? xFindClosestIndex(x, to) : undefined + + const isProtonic = isProton(nucleus) + + const peakPickingOptions: OptionsXYAutoPeaksPicking = { + ...defaultPeakPickingOptions, + smoothY: undefined, + sensitivity: 100, + broadWidth: 0.05, + thresholdFactor: 8, + minMaxRatio, + direction: lookNegative ? 'both' : 'positive', + frequency: originFrequency, + sgOptions: undefined, + } + + const rangesOptions: OptionsPeaksToRanges = { + nucleus, + compile: isProtonic, + frequency: originFrequency, + integrationSum: isProtonic ? spectrum.ranges.options.sum : 100, + frequencyCluster: isProtonic ? 16 : 0, + clean: 0.5, + keepPeaks: true, + joinOverlapRanges: isProtonic, + } + + if (windowFromIndex !== undefined && windowToIndex !== undefined) { + x = x.slice(windowFromIndex, windowToIndex) + re = re.slice(windowFromIndex, windowToIndex) + } + + const ranges = xyAutoRangesPicking( + { x, y: re }, + { + impurities: nucleus === '13C' ? { solvent: solvent || '' } : undefined, + peakPicking: peakPickingOptions, + ranges: rangesOptions, } + ) + spectrum.ranges.values = spectrum.ranges.values.concat( + mapRanges(ranges, spectrum) + ) - const ranges = xyAutoRangesPicking( - { x, y: re }, - { - impurities: nucleus === '13C' ? { solvent: solvent || '' } : undefined, - peakPicking: peakPickingOptions, - ranges: rangesOptions, - }, - ); - - - spectrum.ranges.values = spectrum.ranges.values.concat( - mapRanges(ranges, spectrum), - ); - - updateRangesRelativeValues(spectrum); + updateRangesRelativeValues(spectrum) } - const defaultPeakPickingOptions: OptionsXYAutoPeaksPicking = { - minMaxRatio: 1, - shape: { kind: 'lorentzian' }, - realTopDetection: true, - maxCriteria: true, - smoothY: true, - sensitivity: 100, - broadWidth: 0.25, - broadRatio: 0.0025, - thresholdFactor: 5, - sgOptions: { windowSize: 7, polynomial: 3 }, - frequency: 0 -}; - + minMaxRatio: 1, + shape: { kind: 'lorentzian' }, + realTopDetection: true, + maxCriteria: true, + smoothY: true, + sensitivity: 100, + broadWidth: 0.25, + broadRatio: 0.0025, + thresholdFactor: 5, + sgOptions: { windowSize: 7, polynomial: 3 }, + frequency: 0, +} diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/initSumOptions.ts b/app/scripts/nmr-cli/src/parse/data/data1D/initSumOptions.ts index d02e573..bd396f9 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/initSumOptions.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/initSumOptions.ts @@ -1,56 +1,53 @@ -import type { SumOptions } from '@zakodium/nmr-types'; -import { MF } from 'mf-parser'; -import getAtom from '../../utility/getAtom'; -import { MoleculeExtended } from '../../type/MoleculeExtended'; +import type { SumOptions } from '@zakodium/nmr-types' +import { MF } from 'mf-parser' +import getAtom from '../../utility/getAtom.js' +import { MoleculeExtended } from '../../type/MoleculeExtended.js' export { updateIntegralsRelativeValues, updateRangesRelativeValues, -} from 'nmr-processing'; - +} from 'nmr-processing' export interface SumParams { - nucleus: string; - molecules: MoleculeExtended[]; + nucleus: string + molecules: MoleculeExtended[] } -export type SetSumOptions = Omit; +export type SetSumOptions = Omit export function initSumOptions( options: Partial, - params: SumParams, + params: SumParams ) { let newOptions: SumOptions = { sum: undefined, isSumConstant: true, sumAuto: true, ...options, - }; - const { molecules, nucleus } = params; + } + const { molecules, nucleus } = params if (options.sumAuto && Array.isArray(molecules) && molecules.length > 0) { - const { mf, id } = molecules[0]; - newOptions = { ...newOptions, sumAuto: true, mf, moleculeId: id }; + const { mf, id } = molecules[0] + newOptions = { ...newOptions, sumAuto: true, mf, moleculeId: id } } else { - const { mf, moleculeId, ...resOptions } = newOptions; - newOptions = { ...resOptions, sumAuto: false }; + const { mf, moleculeId, ...resOptions } = newOptions + newOptions = { ...resOptions, sumAuto: false } } if (!newOptions.sum) { - newOptions.sum = getSum(newOptions.mf || null, nucleus); + newOptions.sum = getSum(newOptions.mf || null, nucleus) } - return newOptions; + return newOptions } export function getSum(mf: string | null | undefined, nucleus: string) { - const defaultSum = 100; + const defaultSum = 100 - if (!mf || !nucleus) return defaultSum; + if (!mf || !nucleus) return defaultSum - const atom = getAtom(nucleus); - const atoms = new MF(mf).getInfo().atoms; + const atom = getAtom(nucleus) + const atoms = new MF(mf).getInfo().atoms - return atoms[atom] || defaultSum; + return atoms[atom] || defaultSum } - - diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/initiateDatum1D.ts b/app/scripts/nmr-cli/src/parse/data/data1D/initiateDatum1D.ts index b8cd347..a3186ba 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/initiateDatum1D.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/initiateDatum1D.ts @@ -1,36 +1,33 @@ -import type { - Spectrum1D, -} from '@zakodium/nmrium-core'; -import { Filters1DManager } from 'nmr-processing'; - -import { initSumOptions } from './initSumOptions.js'; -import { initiateRanges } from './initiateRanges.js'; -import { convertDataToFloat64Array } from './convertDataToFloat64Array.js'; -import { initiateFilters } from '../initiateFilters.js'; -import { MoleculeExtended } from '../../type/MoleculeExtended.js'; -import { initiatePeaks } from './initiatePeaks.js'; -import { initiateIntegrals } from './initiateIntegrals.js'; +import type { Spectrum1D } from '@zakodium/nmrium-core' +import { Filters1DManager } from 'nmr-processing' +import { initSumOptions } from './initSumOptions.js' +import { initiateRanges } from './initiateRanges.js' +import { convertDataToFloat64Array } from './convertDataToFloat64Array.js' +import { initiateFilters } from '../initiateFilters.js' +import { MoleculeExtended } from '../../type/MoleculeExtended.js' +import { initiatePeaks } from './initiatePeaks.js' +import { initiateIntegrals } from './initiateIntegrals.js' interface InitiateDatum1DOptions { - molecules?: MoleculeExtended[]; + molecules?: MoleculeExtended[] } export function initiateDatum1D( spectrum: any, - options: InitiateDatum1DOptions = {}, + options: InitiateDatum1DOptions = {} ): Spectrum1D { - const { molecules = [] } = options; + const { molecules = [] } = options - const { integrals, ranges, ...restSpectrum } = spectrum; - const spectrumObj: Spectrum1D = { ...restSpectrum }; - spectrumObj.id = spectrum.id || crypto.randomUUID(); + const { integrals, ranges, ...restSpectrum } = spectrum + const spectrumObj: Spectrum1D = { ...restSpectrum } + spectrumObj.id = spectrum.id || crypto.randomUUID() spectrumObj.display = { isVisible: true, isRealSpectrumVisible: true, ...spectrum.display, - }; + } spectrumObj.info = { nucleus: '1H', // 1H, 13C, 19F, ... @@ -38,40 +35,39 @@ export function initiateDatum1D( isComplex: false, // if isComplex is true that mean it contains real/ imaginary x set, if not hid re/im button . dimension: 1, ...spectrum.info, - }; + } - spectrumObj.originalInfo = spectrumObj.info; + spectrumObj.originalInfo = spectrumObj.info - spectrumObj.meta = { ...spectrum.meta }; + spectrumObj.meta = { ...spectrum.meta } - spectrumObj.customInfo = { ...spectrum.customInfo }; + spectrumObj.customInfo = { ...spectrum.customInfo } - spectrumObj.data = convertDataToFloat64Array(spectrum.data); + spectrumObj.data = convertDataToFloat64Array(spectrum.data) - spectrumObj.originalData = spectrumObj.data; + spectrumObj.originalData = spectrumObj.data - spectrumObj.filters = initiateFilters(spectrum?.filters); //array of object {name: "FilterName", options: FilterOptions = {value | object} } + spectrumObj.filters = initiateFilters(spectrum?.filters) //array of object {name: "FilterName", options: FilterOptions = {value | object} } - const { nucleus } = spectrumObj.info; + const { nucleus } = spectrumObj.info - spectrumObj.peaks = initiatePeaks(spectrum, spectrumObj); + spectrumObj.peaks = initiatePeaks(spectrum, spectrumObj) const integralsOptions = initSumOptions(integrals?.options || {}, { nucleus, molecules, - }); + }) spectrumObj.integrals = initiateIntegrals( spectrum, spectrumObj, - integralsOptions, - ); + integralsOptions + ) const rangesOptions = initSumOptions(ranges?.options || {}, { nucleus, molecules, - }); - spectrumObj.ranges = initiateRanges(spectrum, spectrumObj, rangesOptions); - + }) + spectrumObj.ranges = initiateRanges(spectrum, spectrumObj, rangesOptions) - return spectrumObj; + return spectrumObj } diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/initiateIntegrals.ts b/app/scripts/nmr-cli/src/parse/data/data1D/initiateIntegrals.ts index f5953a6..92f76d0 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/initiateIntegrals.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/initiateIntegrals.ts @@ -1,12 +1,12 @@ -import type { Integrals } from '@zakodium/nmr-types'; -import type { Spectrum1D } from '@zakodium/nmrium-core'; -import merge from 'lodash.merge'; -import { mapIntegrals } from 'nmr-processing'; +import type { Integrals } from '@zakodium/nmr-types' +import type { Spectrum1D } from '@zakodium/nmrium-core' +import merge from 'lodash.merge' +import { mapIntegrals } from 'nmr-processing' export function initiateIntegrals( inputSpectrum: Partial, spectrum: Spectrum1D, - options: Integrals['options'], + options: Integrals['options'] ) { return merge( { @@ -15,6 +15,6 @@ export function initiateIntegrals( }, { values: mapIntegrals(inputSpectrum?.integrals?.values || [], spectrum), - }, - ); + } + ) } diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/initiatePeaks.ts b/app/scripts/nmr-cli/src/parse/data/data1D/initiatePeaks.ts index c998297..e2e0a30 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/initiatePeaks.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/initiatePeaks.ts @@ -1,13 +1,13 @@ -import type { Peaks } from '@zakodium/nmr-types'; -import type { Spectrum1D } from '@zakodium/nmrium-core'; -import merge from 'lodash.merge'; -import { mapPeaks } from 'nmr-processing'; +import type { Peaks } from '@zakodium/nmr-types' +import type { Spectrum1D } from '@zakodium/nmrium-core' +import merge from 'lodash.merge' +import { mapPeaks } from 'nmr-processing' export function initiatePeaks( inputSpectrum: Partial, - spectrum: Spectrum1D, + spectrum: Spectrum1D ): Peaks { return merge({ values: [], options: {} }, inputSpectrum.peaks, { values: mapPeaks(inputSpectrum?.peaks?.values || [], spectrum), - }); + }) } diff --git a/app/scripts/nmr-cli/src/parse/data/data1D/initiateRanges.ts b/app/scripts/nmr-cli/src/parse/data/data1D/initiateRanges.ts index 529eefe..35ef1ce 100644 --- a/app/scripts/nmr-cli/src/parse/data/data1D/initiateRanges.ts +++ b/app/scripts/nmr-cli/src/parse/data/data1D/initiateRanges.ts @@ -1,12 +1,12 @@ -import type { Ranges } from '@zakodium/nmr-types'; -import type { Spectrum1D } from '@zakodium/nmrium-core'; -import merge from 'lodash.merge'; -import { mapRanges } from 'nmr-processing'; +import type { Ranges } from '@zakodium/nmr-types' +import type { Spectrum1D } from '@zakodium/nmrium-core' +import merge from 'lodash.merge' +import { mapRanges } from 'nmr-processing' export function initiateRanges( inputSpectrum: Partial, spectrum: Spectrum1D, - options: Ranges['options'], + options: Ranges['options'] ) { return merge( { @@ -15,6 +15,6 @@ export function initiateRanges( }, { values: mapRanges(inputSpectrum?.ranges?.values || [], spectrum), - }, - ); + } + ) } diff --git a/app/scripts/nmr-cli/src/parse/data/data2d/detectZones.ts b/app/scripts/nmr-cli/src/parse/data/data2d/detectZones.ts index 62cfe28..2e6fb21 100644 --- a/app/scripts/nmr-cli/src/parse/data/data2d/detectZones.ts +++ b/app/scripts/nmr-cli/src/parse/data/data2d/detectZones.ts @@ -1,24 +1,23 @@ -import { Spectrum2D } from "@zakodium/nmrium-core"; -import { isFt2DSpectrum } from "./isSpectrum2D"; -import { mapZones } from "nmr-processing"; -import { getDetectionZones } from "./getDetectionZones"; -import { Zone } from "@zakodium/nmr-types"; +import { Spectrum2D } from '@zakodium/nmrium-core' +import { isFt2DSpectrum } from './isSpectrum2D.js' +import { mapZones } from 'nmr-processing' +import { getDetectionZones } from './getDetectionZones.js' +import { Zone } from '@zakodium/nmr-types' export function detectZones(spectrum: Spectrum2D) { + if (!isFt2DSpectrum(spectrum)) return - if (!isFt2DSpectrum(spectrum)) return; + const { data } = spectrum - const { data } = spectrum; + const { + rr: { minX, maxX, minY, maxY }, + } = data + const detectionOptions = { + selectedZone: { fromX: minX, toX: maxX, fromY: minY, toY: maxY }, + thresholdFactor: 1, + maxPercentCutOff: 0.03, + } - const { rr: { minX, maxX, minY, maxY } } = data; - const detectionOptions = { - selectedZone: { fromX: minX, toX: maxX, fromY: minY, toY: maxY }, - thresholdFactor: 1, - maxPercentCutOff: 0.03, - }; - - const zones = getDetectionZones(spectrum, detectionOptions); - spectrum.zones.values = mapZones(zones as Zone[], spectrum); - - -} \ No newline at end of file + const zones = getDetectionZones(spectrum, detectionOptions) + spectrum.zones.values = mapZones(zones as Zone[], spectrum) +} diff --git a/app/scripts/nmr-cli/src/parse/data/data2d/getDetectionZones.ts b/app/scripts/nmr-cli/src/parse/data/data2d/getDetectionZones.ts index 03c62a4..bf14fc7 100644 --- a/app/scripts/nmr-cli/src/parse/data/data2d/getDetectionZones.ts +++ b/app/scripts/nmr-cli/src/parse/data/data2d/getDetectionZones.ts @@ -1,18 +1,18 @@ -import type { Spectrum2D } from '@zakodium/nmrium-core'; -import { xyzAutoZonesPicking } from 'nmr-processing'; +import type { Spectrum2D } from '@zakodium/nmrium-core' +import { xyzAutoZonesPicking } from 'nmr-processing' export interface DetectionZonesOptions { selectedZone: { - fromX: number; - fromY: number; - toX: number; - toY: number; - }; - thresholdFactor: number; - maxPercentCutOff: number; - tolerances?: number[]; - convolutionByFFT?: boolean; - enhanceSymmetry?: boolean; + fromX: number + fromY: number + toX: number + toY: number + } + thresholdFactor: number + maxPercentCutOff: number + tolerances?: number[] + convolutionByFFT?: boolean + enhanceSymmetry?: boolean } /** @@ -28,21 +28,21 @@ export interface DetectionZonesOptions { */ export function getDetectionZones( spectrum: Spectrum2D, - options: DetectionZonesOptions, + options: DetectionZonesOptions ) { - let dataMatrix = {}; - const { selectedZone } = options; + let dataMatrix = {} + const { selectedZone } = options if (selectedZone) { - options.enhanceSymmetry = false; - dataMatrix = getSubMatrix(spectrum, selectedZone); + options.enhanceSymmetry = false + dataMatrix = getSubMatrix(spectrum, selectedZone) } else { - dataMatrix = spectrum.data; + dataMatrix = spectrum.data } return autoZonesDetection(dataMatrix, { ...options, info: spectrum.info, - }); + }) } function autoZonesDetection(data: any, options: any) { @@ -53,9 +53,9 @@ function autoZonesDetection(data: any, options: any) { maxPercentCutOff, convolutionByFFT, info: { nucleus: nuclei, originFrequency }, - } = options; + } = options - const { enhanceSymmetry = nuclei[0] === nuclei[1] } = options; + const { enhanceSymmetry = nuclei[0] === nuclei[1] } = options const zones = xyzAutoZonesPicking(data, { nuclei, @@ -67,29 +67,29 @@ function autoZonesDetection(data: any, options: any) { maxPercentCutOff, enhanceSymmetry, convolutionByFFT, - }); + }) - return zones; + return zones } function getSubMatrix(datum: any, selectedZone: any) { - const { fromX, toX, fromY, toY } = selectedZone; - const data = datum.data.rr; - const xStep = (data.maxX - data.minX) / (data.z[0].length - 1); - const yStep = (data.maxY - data.minY) / (data.z.length - 1); - let xIndexFrom = Math.max(Math.floor((fromX - data.minX) / xStep), 0); - let yIndexFrom = Math.max(Math.floor((fromY - data.minY) / yStep), 0); + const { fromX, toX, fromY, toY } = selectedZone + const data = datum.data.rr + const xStep = (data.maxX - data.minX) / (data.z[0].length - 1) + const yStep = (data.maxY - data.minY) / (data.z.length - 1) + let xIndexFrom = Math.max(Math.floor((fromX - data.minX) / xStep), 0) + let yIndexFrom = Math.max(Math.floor((fromY - data.minY) / yStep), 0) let xIndexTo = Math.min( Math.floor((toX - data.minX) / xStep), - data.z[0].length - 1, - ); + data.z[0].length - 1 + ) let yIndexTo = Math.min( Math.floor((toY - data.minY) / yStep), - data.z.length - 1, - ); + data.z.length - 1 + ) - if (xIndexFrom > xIndexTo) [xIndexFrom, xIndexTo] = [xIndexTo, xIndexFrom]; - if (yIndexFrom > yIndexTo) [yIndexFrom, yIndexTo] = [yIndexTo, yIndexFrom]; + if (xIndexFrom > xIndexTo) [xIndexFrom, xIndexTo] = [xIndexTo, xIndexFrom] + if (yIndexFrom > yIndexTo) [yIndexFrom, yIndexTo] = [yIndexTo, yIndexFrom] const dataMatrix: any = { z: [], @@ -97,25 +97,25 @@ function getSubMatrix(datum: any, selectedZone: any) { minX: data.minX + xIndexFrom * xStep, maxY: data.minY + yIndexTo * yStep, minY: data.minY + yIndexFrom * yStep, - }; - let maxZ = Number.MIN_SAFE_INTEGER; - let minZ = Number.MAX_SAFE_INTEGER; + } + let maxZ = Number.MIN_SAFE_INTEGER + let minZ = Number.MAX_SAFE_INTEGER - const nbXPoints = xIndexTo - xIndexFrom + 1; + const nbXPoints = xIndexTo - xIndexFrom + 1 for (let j = yIndexFrom; j < yIndexTo; j++) { - const row = new Float32Array(nbXPoints); - let xIndex = xIndexFrom; + const row = new Float32Array(nbXPoints) + let xIndex = xIndexFrom for (let i = 0; i < nbXPoints; i++) { - row[i] = data.z[j][xIndex++]; + row[i] = data.z[j][xIndex++] } for (const rowValue of row) { - if (maxZ < rowValue) maxZ = rowValue; - if (minZ > rowValue) minZ = rowValue; + if (maxZ < rowValue) maxZ = rowValue + if (minZ > rowValue) minZ = rowValue } - dataMatrix.z.push(Array.from(row)); + dataMatrix.z.push(Array.from(row)) } - dataMatrix.minZ = minZ; - dataMatrix.maxZ = maxZ; - return dataMatrix; + dataMatrix.minZ = minZ + dataMatrix.maxZ = maxZ + return dataMatrix } diff --git a/app/scripts/nmr-cli/src/parse/data/data2d/initiateDatum2D.ts b/app/scripts/nmr-cli/src/parse/data/data2d/initiateDatum2D.ts index 3676f1e..9d88fcb 100644 --- a/app/scripts/nmr-cli/src/parse/data/data2d/initiateDatum2D.ts +++ b/app/scripts/nmr-cli/src/parse/data/data2d/initiateDatum2D.ts @@ -1,14 +1,10 @@ -import type { - Spectrum2D, -} from '@zakodium/nmrium-core'; -import { Filters2DManager } from 'nmr-processing'; +import type { Spectrum2D } from '@zakodium/nmrium-core' +import { Filters2DManager } from 'nmr-processing' +import { initiateZones } from './initiateZones.js' +import { initiateFilters } from '../initiateFilters.js' -import { initiateZones } from './initiateZones.js'; -import { initiateFilters } from '../initiateFilters.js'; - -const defaultMinMax = { z: [], minX: 0, minY: 0, maxX: 0, maxY: 0 }; - +const defaultMinMax = { z: [], minX: 0, minY: 0, maxX: 0, maxY: 0 } function initiateDisplay(spectrum: any) { return { @@ -17,7 +13,7 @@ function initiateDisplay(spectrum: any) { isVisible: true, dimension: 2, ...spectrum.display, - }; + } } function initiateInfo(spectrum: any) { @@ -28,42 +24,39 @@ function initiateInfo(spectrum: any) { isComplex: false, // if isComplex is true that mean it contains real/ imaginary x set, if not hid re/im button . dimension: 2, ...spectrum.info, - }; + } } +export function initiateDatum2D(spectrum: any): Spectrum2D { + const datum: any = { ...spectrum } -export function initiateDatum2D( - spectrum: any, -): Spectrum2D { - const datum: any = { ...spectrum }; - - datum.id = spectrum.id || crypto.randomUUID(); + datum.id = spectrum.id || crypto.randomUUID() - datum.display = initiateDisplay(spectrum); + datum.display = initiateDisplay(spectrum) - datum.info = initiateInfo(spectrum); + datum.info = initiateInfo(spectrum) - datum.originalInfo = datum.info; + datum.originalInfo = datum.info - datum.meta = { ...spectrum.meta }; + datum.meta = { ...spectrum.meta } - datum.customInfo = { ...spectrum.customInfo }; + datum.customInfo = { ...spectrum.customInfo } - datum.data = getData(datum, spectrum); - datum.originalData = datum.data; - datum.filters = initiateFilters(spectrum?.filters); + datum.data = getData(datum, spectrum) + datum.originalData = datum.data + datum.filters = initiateFilters(spectrum?.filters) - datum.zones = initiateZones(spectrum, datum as Spectrum2D); + datum.zones = initiateZones(spectrum, datum as Spectrum2D) //reapply filters after load the original data - return datum; + return datum } function getData(datum: any, options: any) { if (datum.info.isFid) { - const { re = defaultMinMax, im = defaultMinMax } = options.data; - return { re, im }; + const { re = defaultMinMax, im = defaultMinMax } = options.data + return { re, im } } - return { rr: defaultMinMax, ...options.data }; + return { rr: defaultMinMax, ...options.data } } diff --git a/app/scripts/nmr-cli/src/parse/data/data2d/initiateZones.ts b/app/scripts/nmr-cli/src/parse/data/data2d/initiateZones.ts index cc16095..a173ae2 100644 --- a/app/scripts/nmr-cli/src/parse/data/data2d/initiateZones.ts +++ b/app/scripts/nmr-cli/src/parse/data/data2d/initiateZones.ts @@ -1,11 +1,11 @@ -import type { Zones } from '@zakodium/nmr-types'; -import type { Spectrum2D } from '@zakodium/nmrium-core'; -import merge from 'lodash.merge'; -import { mapZones } from 'nmr-processing'; +import type { Zones } from '@zakodium/nmr-types' +import type { Spectrum2D } from '@zakodium/nmrium-core' +import merge from 'lodash.merge' +import { mapZones } from 'nmr-processing' export function initiateZones( options: Partial<{ zones: Zones }>, - spectrum: Spectrum2D, + spectrum: Spectrum2D ) { return merge( { @@ -19,6 +19,6 @@ export function initiateZones( options.zones, { values: mapZones(options?.zones?.values || [], spectrum), - }, - ); + } + ) } diff --git a/app/scripts/nmr-cli/src/parse/data/data2d/isSpectrum2D.ts b/app/scripts/nmr-cli/src/parse/data/data2d/isSpectrum2D.ts index 480309b..4f88eab 100644 --- a/app/scripts/nmr-cli/src/parse/data/data2d/isSpectrum2D.ts +++ b/app/scripts/nmr-cli/src/parse/data/data2d/isSpectrum2D.ts @@ -1,36 +1,36 @@ -import type { Spectrum2D, Spectrum } from '@zakodium/nmrium-core'; -import type { NmrData2D, NmrData2DFid, NmrData2DFt } from 'cheminfo-types'; +import type { Spectrum2D, Spectrum } from '@zakodium/nmrium-core' +import type { NmrData2D, NmrData2DFid, NmrData2DFt } from 'cheminfo-types' export function isSpectrum2D( - spectrum: Spectrum | undefined, + spectrum: Spectrum | undefined ): spectrum is Spectrum2D { - return spectrum?.info.dimension === 2; + return spectrum?.info.dimension === 2 } function isQuadrantsData(data: NmrData2D): data is NmrData2DFt { - return 'rr' in data && 'ri' in data && 'ir' in data && 'ii' in data; + return 'rr' in data && 'ri' in data && 'ir' in data && 'ii' in data } function isFt2DData(data: NmrData2D): data is NmrData2DFt { - return 'rr' in data; + return 'rr' in data } export function isFid2DData(data: NmrData2D): data is NmrData2DFid { - return 're' in data; + return 're' in data } export function isFid2DSpectrum( - spectrum: Spectrum, + spectrum: Spectrum ): spectrum is Spectrum2D & { data: NmrData2DFid } { - return isSpectrum2D(spectrum) && isFid2DData(spectrum.data); + return isSpectrum2D(spectrum) && isFid2DData(spectrum.data) } export function isFt2DSpectrum( - spectrum: Spectrum, + spectrum: Spectrum ): spectrum is Spectrum2D & { data: NmrData2DFt } { - return isSpectrum2D(spectrum) && isFt2DData(spectrum.data); + return isSpectrum2D(spectrum) && isFt2DData(spectrum.data) } export function isQuadrants2DSpectrum( - spectrum: Spectrum, + spectrum: Spectrum ): spectrum is Spectrum2D & { data: NmrData2DFt } { - return isSpectrum2D(spectrum) && isQuadrantsData(spectrum.data); + return isSpectrum2D(spectrum) && isQuadrantsData(spectrum.data) } diff --git a/app/scripts/nmr-cli/src/parse/data/initiateFilters.ts b/app/scripts/nmr-cli/src/parse/data/initiateFilters.ts index 53e1e79..3aa7a57 100644 --- a/app/scripts/nmr-cli/src/parse/data/initiateFilters.ts +++ b/app/scripts/nmr-cli/src/parse/data/initiateFilters.ts @@ -1,8 +1,8 @@ export function initiateFilters(inputFilters: any): any { - if (!inputFilters || !Array.isArray(inputFilters)) return []; + if (!inputFilters || !Array.isArray(inputFilters)) return [] - return inputFilters.map((filter) => ({ + return inputFilters.map(filter => ({ ...filter, id: filter?.id || crypto.randomUUID(), - })); + })) } diff --git a/app/scripts/nmr-cli/src/parse/prase-spectra.ts b/app/scripts/nmr-cli/src/parse/prase-spectra.ts index e496b12..52ec6d1 100644 --- a/app/scripts/nmr-cli/src/parse/prase-spectra.ts +++ b/app/scripts/nmr-cli/src/parse/prase-spectra.ts @@ -1,37 +1,39 @@ -import { join, isAbsolute } from 'path' -import { NmriumData, ParsingOptions, type NmriumState } from '@zakodium/nmrium-core' +import { join, isAbsolute } from 'node:path' +import { + NmriumData, + ParsingOptions, + type NmriumState, +} from '@zakodium/nmrium-core' import init from '@zakodium/nmrium-core-plugins' import playwright from 'playwright' import { FileCollection } from 'file-collection' -import { FileOptionsArgs } from '..' -import { isSpectrum2D } from './data/data2d/isSpectrum2D' -import { initiateDatum2D } from './data/data2d/initiateDatum2D' -import { initiateDatum1D } from './data/data1D/initiateDatum1D' -import { detectZones } from './data/data2d/detectZones' -import { detectRanges } from './data/data1D/detectRanges' +import { FileOptionsArgs } from '../index.js' +import { isSpectrum2D } from './data/data2d/isSpectrum2D.js' +import { initiateDatum2D } from './data/data2d/initiateDatum2D.js' +import { initiateDatum1D } from './data/data1D/initiateDatum1D.js' +import { detectZones } from './data/data2d/detectZones.js' +import { detectRanges } from './data/data1D/detectRanges.js' import { Filters1DManager, Filters2DManager } from 'nmr-processing' -import yargs from 'yargs' -import { createWriteStream } from 'fs' -import { JsonStreamStringify } from 'json-stream-stringify'; +import { type ArgumentsCamelCase } from 'yargs' +import { createWriteStream } from 'node:fs' +import { JsonStreamStringify } from 'json-stream-stringify' import { FifoLogger } from 'fifo-logger' -type RequiredKey = Omit & Required>; +type RequiredKey = Omit & Required> function toMessage(e: unknown): string { return e instanceof Error ? e.message : String(e) } - const parsingOptions: ParsingOptions = { onLoadProcessing: { autoProcessing: true }, selector: { general: { dataSelection: 'preferFT' } }, experimentalFeatures: true, -}; - +} interface Snapshot { - id: string; - image: string | null; + id: string + image: string | null } const core = init() @@ -44,122 +46,148 @@ function generateNMRiumURL() { } async function launchBrowser() { - return playwright.firefox.launch(); + return playwright.firefox.launch() } -async function captureSpectraViewAsBase64(nmriumState: Partial, logger: FifoLogger): Promise { - const { data: { spectra } = { spectra: [] }, version } = nmriumState; +async function captureSpectraViewAsBase64( + nmriumState: Partial, + logger: FifoLogger +): Promise { + const { data: { spectra } = { spectra: [] }, version } = nmriumState - if (!spectra?.length) return []; + if (!spectra?.length) return [] - const url = generateNMRiumURL(); - const snapshots: Snapshot[] = []; - let browser = await launchBrowser(); + const url = generateNMRiumURL() + const snapshots: Snapshot[] = [] + let browser = await launchBrowser() for (const spectrum of spectra) { - let context = null; + let context = null try { // recreate browser if it has crashed if (!browser.isConnected()) { - browser = await launchBrowser(); + browser = await launchBrowser() } - context = await browser.newContext(playwright.devices['Desktop Chrome HiDPI']); - const page = await context.newPage(); + context = await browser.newContext( + playwright.devices['Desktop Chrome HiDPI'] + ) + const page = await context.newPage() - await page.goto(url); - await page.locator('text=Loading').waitFor({ state: 'hidden' }); + await page.goto(url) + await page.locator('text=Loading').waitFor({ state: 'hidden' }) const stringObject = JSON.stringify( { version, data: { spectra: [{ ...spectrum }] } }, - (key, value: unknown) => ArrayBuffer.isView(value) ? Array.from(value as unknown as Iterable) : value - ); + (key, value: unknown) => + ArrayBuffer.isView(value) + ? Array.from(value as unknown as Iterable) + : value + ) await page.evaluate(` window.postMessage({ type: "nmr-wrapper:load", data: { data: ${stringObject}, type: "nmrium" } }, '*'); - `); + `) - await page.locator('text=Loading').waitFor({ state: 'hidden' }); - - const snapshot = await page.locator('#nmrSVG .container').screenshot(); - snapshots.push({ id: spectrum.id, image: snapshot.toString('base64') }); + await page.locator('text=Loading').waitFor({ state: 'hidden' }) + const snapshot = await page.locator('#nmrSVG .container').screenshot() + snapshots.push({ id: spectrum.id, image: snapshot.toString('base64') }) } catch (e) { - logger.error({ id: spectrum.id, stage: 'snapshot', details: toMessage(e) }, `Failed to capture snapshot for spectrum: ${spectrum.id}`); + logger.error( + { id: spectrum.id, stage: 'snapshot', details: toMessage(e) }, + `Failed to capture snapshot for spectrum: ${spectrum.id}` + ) // browser crashed — close and recreate for next spectrum - await browser.close().catch(() => { }); - browser = await launchBrowser(); - + await browser.close().catch(() => {}) + browser = await launchBrowser() } finally { - await context?.close().catch(() => { }); + await context?.close().catch(() => {}) } } - await browser.close().catch(() => { }); - return snapshots; + await browser.close().catch(() => {}) + return snapshots } - interface ProcessSpectraOptions { - autoDetection: boolean; autoProcessing: boolean; + autoDetection: boolean + autoProcessing: boolean } -function processSpectra(data: NmriumData, options: ProcessSpectraOptions, logger: FifoLogger) { - +function processSpectra( + data: NmriumData, + options: ProcessSpectraOptions, + logger: FifoLogger +) { const { autoDetection = false, autoProcessing = false } = options for (let index = 0; index < data.spectra.length; index++) { const inputSpectrum = data.spectra[index] - const is2D = isSpectrum2D(inputSpectrum); - let spectrum = null; + const is2D = isSpectrum2D(inputSpectrum) + let spectrum = null try { - - spectrum = is2D ? initiateDatum2D(inputSpectrum) : initiateDatum1D(inputSpectrum); + spectrum = is2D + ? initiateDatum2D(inputSpectrum) + : initiateDatum1D(inputSpectrum) } catch (e) { - logger.error({ id: inputSpectrum.id, stage: 'parsing', details: toMessage(e) }, `Failed to parse spectrum: ${inputSpectrum.id}`); - continue; + logger.error( + { id: inputSpectrum.id, stage: 'parsing', details: toMessage(e) }, + `Failed to parse spectrum: ${inputSpectrum.id}` + ) + continue } if (autoProcessing) { try { - - isSpectrum2D(spectrum) ? Filters2DManager.reapplyFilters(spectrum) : Filters1DManager.reapplyFilters(spectrum); - logger.info({ id: inputSpectrum.id, stage: 'processing' }, `Processed spectrum: ${inputSpectrum.id}`); - + isSpectrum2D(spectrum) + ? Filters2DManager.reapplyFilters(spectrum) + : Filters1DManager.reapplyFilters(spectrum) + logger.info( + { id: inputSpectrum.id, stage: 'processing' }, + `Processed spectrum: ${inputSpectrum.id}` + ) } catch (e) { - logger.error({ id: inputSpectrum.id, stage: 'processing', details: toMessage(e) }, `Failed to process spectrum: ${inputSpectrum.id}`); + logger.error( + { id: inputSpectrum.id, stage: 'processing', details: toMessage(e) }, + `Failed to process spectrum: ${inputSpectrum.id}` + ) } } if (autoDetection && spectrum.info.isFt) { try { - isSpectrum2D(spectrum) ? detectZones(spectrum) : detectRanges(spectrum); - logger.info({ id: inputSpectrum.id, stage: 'detection' }, `Detected peaks for spectrum: ${inputSpectrum.id}`); + isSpectrum2D(spectrum) ? detectZones(spectrum) : detectRanges(spectrum) + logger.info( + { id: inputSpectrum.id, stage: 'detection' }, + `Detected peaks for spectrum: ${inputSpectrum.id}` + ) } catch (e) { - logger.error({ id: inputSpectrum.id, stage: 'detection', details: toMessage(e) }, `Failed to detect peaks for spectrum: ${inputSpectrum.id}`); + logger.error( + { id: inputSpectrum.id, stage: 'detection', details: toMessage(e) }, + `Failed to detect peaks for spectrum: ${inputSpectrum.id}` + ) } } - if (!spectrum) continue; + if (!spectrum) continue - data.spectra[index] = spectrum; + data.spectra[index] = spectrum } - - } function outputResult(result: any, outputPath?: string) { - const stream = new JsonStreamStringify(result); + const stream = new JsonStreamStringify(result) if (outputPath) { - const writeStream = createWriteStream(outputPath); - stream.pipe(writeStream); + const writeStream = createWriteStream(outputPath) + stream.pipe(writeStream) writeStream.on('finish', () => { - process.stderr.write(`Output written to: ${outputPath}\n`); - }); + process.stderr.write(`Output written to: ${outputPath}\n`) + }) } else { - stream.pipe(process.stdout); + stream.pipe(process.stdout) } } @@ -168,32 +196,37 @@ async function processAndSerialize( options: FileOptionsArgs, logger: FifoLogger ) { - const { s: enableSnapshot = false, p: autoProcessing = false, d: autoDetection = false, o, r } = options; + const { + s: enableSnapshot = false, + p: autoProcessing = false, + d: autoDetection = false, + o, + r, + } = options if (nmriumState.data) { - processSpectra(nmriumState.data, { autoDetection, autoProcessing }, logger); + processSpectra(nmriumState.data, { autoDetection, autoProcessing }, logger) } const images: Snapshot[] = enableSnapshot ? await captureSpectraViewAsBase64(nmriumState, logger) - : []; + : [] const { data, version } = core.serializeNmriumState( nmriumState as NmriumState, - { includeData: r ? 'rawData' : 'dataSource', }, - - ); + { includeData: r ? 'rawData' : 'dataSource' } + ) // include the meta and info object in case of serialize as dataSource - const spectra: any = data?.spectra || []; + const spectra: any = data?.spectra || [] if (!r) { for (let i = 0; i < spectra.length; i++) { - const { info = {}, meta = {} } = nmriumState.data?.spectra[i] || {}; + const { info = {}, meta = {} } = nmriumState.data?.spectra[i] || {} spectra[i] = { ...spectra[i], info, meta } } } - const logs = logger.getLogs(); - outputResult({ nmriumState: { data, version }, images, logs }, o); + const logs = logger.getLogs() + outputResult({ nmriumState: { data, version }, images, logs }, o) } function buildWebSource(url: string) { @@ -208,18 +241,28 @@ function buildWebSource(url: string) { } } -async function loadSpectrumFromURL(options: RequiredKey, logger: FifoLogger) { - const { u: url, include, exclude } = options; +async function loadSpectrumFromURL( + options: RequiredKey, + logger: FifoLogger +) { + const { u: url, include, exclude } = options const source = buildWebSource(url) - const { state } = await core.readFromWebSource(source, { ...parsingOptions, fileFilter: { include, exclude }, logger }); + const { state } = await core.readFromWebSource(source, { + ...parsingOptions, + fileFilter: { include, exclude }, + logger, + }) processAndSerialize(state, options, logger) - } -function loadFileCollection(path: string, include?: string[], exclude?: string[]) { +function loadFileCollection( + path: string, + include?: string[], + exclude?: string[] +) { const dirPath = isAbsolute(path) ? path : join(process.cwd(), path) return FileCollection.fromPath(dirPath, { @@ -228,40 +271,43 @@ function loadFileCollection(path: string, include?: string[], exclude?: string[] }) } -async function loadSpectrumFromFilePath(options: RequiredKey, logger: FifoLogger) { - const { dir: path, include, exclude } = options; +async function loadSpectrumFromFilePath( + options: RequiredKey, + logger: FifoLogger +) { + const { dir: path, include, exclude } = options const fileCollection = await loadFileCollection(path, include, exclude) - const { - state - } = await core.read(fileCollection, { ...parsingOptions, logger }) + const { state } = await core.read(fileCollection, { + ...parsingOptions, + logger, + }) processAndSerialize(state, options, logger) - } +function parseSpectra(argv: ArgumentsCamelCase) { + const logger = new FifoLogger() -function parseSpectra(argv: yargs.ArgumentsCamelCase -) { - const logger = new FifoLogger(); - - const { u, dir } = argv; + const { u, dir } = argv // Handle parsing the spectra file logic based on argv options if (u) { - loadSpectrumFromURL({ u, ...argv }, logger); + loadSpectrumFromURL({ u, ...argv }, logger) } - if (dir) { - loadSpectrumFromFilePath({ dir, ...argv }, logger); + loadSpectrumFromFilePath({ dir, ...argv }, logger) } - - - } - - - -export { loadSpectrumFromFilePath, loadSpectrumFromURL, parseSpectra, processSpectra, parsingOptions, core, buildWebSource, loadFileCollection } +export { + loadSpectrumFromFilePath, + loadSpectrumFromURL, + parseSpectra, + processSpectra, + parsingOptions, + core, + buildWebSource, + loadFileCollection, +} diff --git a/app/scripts/nmr-cli/src/parse/type/MoleculeExtended.ts b/app/scripts/nmr-cli/src/parse/type/MoleculeExtended.ts index 7e71a00..601a66f 100644 --- a/app/scripts/nmr-cli/src/parse/type/MoleculeExtended.ts +++ b/app/scripts/nmr-cli/src/parse/type/MoleculeExtended.ts @@ -1,11 +1,12 @@ -import { StateMolecule } from "@zakodium/nmrium-core"; +import { StateMolecule } from '@zakodium/nmrium-core' export interface MoleculeExtended - extends Required>, + extends + Required>, Omit { - mf: string; - em: number; - mw: number; - svg: string; - atoms: Record; -} \ No newline at end of file + mf: string + em: number + mw: number + svg: string + atoms: Record +} diff --git a/app/scripts/nmr-cli/src/parse/utility/getAtom.ts b/app/scripts/nmr-cli/src/parse/utility/getAtom.ts index 68e66ec..da09e78 100644 --- a/app/scripts/nmr-cli/src/parse/utility/getAtom.ts +++ b/app/scripts/nmr-cli/src/parse/utility/getAtom.ts @@ -1,3 +1,3 @@ export default function getAtom(nucleus: string): string { - return nucleus?.replaceAll(/\d/g, '') || ''; + return nucleus?.replaceAll(/\d/g, '') || '' } diff --git a/app/scripts/nmr-cli/src/parse/utility/isProton.ts b/app/scripts/nmr-cli/src/parse/utility/isProton.ts index 85f6e6e..00f3e6e 100644 --- a/app/scripts/nmr-cli/src/parse/utility/isProton.ts +++ b/app/scripts/nmr-cli/src/parse/utility/isProton.ts @@ -1,3 +1,3 @@ export function isProton(nucleus: string) { - return nucleus === '1H'; + return nucleus === '1H' } diff --git a/app/scripts/nmr-cli/src/peaks-to-nmrium.ts b/app/scripts/nmr-cli/src/peaks-to-nmrium.ts index d61df95..2a9ccab 100644 --- a/app/scripts/nmr-cli/src/peaks-to-nmrium.ts +++ b/app/scripts/nmr-cli/src/peaks-to-nmrium.ts @@ -1,7 +1,7 @@ import { peaksToXY } from 'nmr-processing' import { CURRENT_EXPORT_VERSION } from '@zakodium/nmrium-core' import type { NMRPeak1D } from '@zakodium/nmr-types' -import { castToArray } from './utilities/castToArray' +import { castToArray } from './utilities/castToArray.js' interface PeakInput { x: number @@ -39,7 +39,7 @@ function generateNMRiumFromPeaks(input: PeaksToNMRiumInput) { } const defaultWidth = 1 - const nmrPeaks: NMRPeak1D[] = peaks.map((peak) => ({ + const nmrPeaks: NMRPeak1D[] = peaks.map(peak => ({ x: peak.x, y: peak.y ?? 1, width: peak.width ?? defaultWidth, diff --git a/app/scripts/nmr-cli/src/prediction/engines/base.ts b/app/scripts/nmr-cli/src/prediction/engines/base.ts index c60c1bb..bf2a34b 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/base.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/base.ts @@ -15,50 +15,50 @@ export type Nucleus = '1H' | '13C' * Map from experiment name to nucleus */ export const experimentToNucleus: Record = { - proton: '1H', - carbon: '13C', + proton: '1H', + carbon: '13C', } /** * Base interface that all engines must implement */ export interface Engine { - /** Unique engine identifier (e.g., 'nmrdb.org') */ - readonly id: string + /** Unique engine identifier (e.g., 'nmrdb.org') */ + readonly id: string - readonly name: string - readonly description: string - readonly supportedSpectra: readonly Experiment[] + readonly name: string + readonly description: string + readonly supportedSpectra: readonly Experiment[] - /** Command-line options specific to this engine */ - readonly options: Record + /** Command-line options specific to this engine */ + readonly options: Record - /** List of required option keys */ - readonly requiredOptions: readonly string[] + /** List of required option keys */ + readonly requiredOptions: readonly string[] - /** - * Build the payload options for the API request - * @param argv - Command line arguments - * @returns Options object to send in the API payload - */ - buildPayloadOptions(argv: Record): any + /** + * Build the payload options for the API request + * @param argv - Command line arguments + * @returns Options object to send in the API payload + */ + buildPayloadOptions(argv: Record): any - /** - * Predict and generate spectra - * This is the main entry point for prediction - * @param structure - MOL file content - * @param options - Command line options - * @returns Array of generated spectra - */ - predict( - structure: string, - options: Record, - ): Promise + /** + * Predict and generate spectra + * This is the main entry point for prediction + * @param structure - MOL file content + * @param options - Command line options + * @returns Array of generated spectra + */ + predict( + structure: string, + options: Record + ): Promise - /** - * Optional: Custom validation beyond required options - * @param argv - Command line arguments - * @returns true if valid, error message if invalid - */ - validate?(argv: Record): true | string -} \ No newline at end of file + /** + * Optional: Custom validation beyond required options + * @param argv - Command line arguments + * @returns true if valid, error message if invalid + */ + validate?(argv: Record): true | string +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/index.ts b/app/scripts/nmr-cli/src/prediction/engines/index.ts index c1bfe8c..ae601d6 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/index.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/index.ts @@ -1,5 +1,5 @@ -import './nmrdb/nmrdb.engine' -import './nmrshift/nmrshift.engine' +import './nmrdb/nmrdb.engine.js' +import './nmrshift/nmrshift.engine.js' -export { engineRegistry } from './registry'; -export type { Engine } from './base'; +export { engineRegistry } from './registry.js' +export type { Engine } from './base.js' diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/checkFromTo.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/checkFromTo.ts index 1720c0c..bc63e53 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/checkFromTo.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/checkFromTo.ts @@ -1,91 +1,89 @@ -import { xMinMaxValues } from "ml-spectra-processing" -import { Experiment } from "../../base" -import { isProton } from "../../../../utilities/isProton" -import { Prediction1D, Prediction2D } from "nmr-processing" -import { PredictedSpectraResult, PredictionOptions } from "../nmrdb.engine" +import { xMinMaxValues } from 'ml-spectra-processing' +import { Experiment } from '../../base.js' +import { isProton } from '../../../../utilities/isProton.js' +import { Prediction1D, Prediction2D } from 'nmr-processing' +import { PredictedSpectraResult, PredictionOptions } from '../nmrdb.engine.js' export function checkFromTo( - predictedSpectra: PredictedSpectraResult, - inputOptions: PredictionOptions, + predictedSpectra: PredictedSpectraResult, + inputOptions: PredictionOptions ) { - const setFromTo = (inputOptions: any, nucleus: any, fromTo: any) => { - inputOptions['1d'][nucleus].to = fromTo.to - inputOptions['1d'][nucleus].from = fromTo.from - if (fromTo.signalsOutOfRange) { - signalsOutOfRange[nucleus] = true - } + const setFromTo = (inputOptions: any, nucleus: any, fromTo: any) => { + inputOptions['1d'][nucleus].to = fromTo.to + inputOptions['1d'][nucleus].from = fromTo.from + if (fromTo.signalsOutOfRange) { + signalsOutOfRange[nucleus] = true } + } - const { autoExtendRange, spectra } = inputOptions - const signalsOutOfRange: Record = {} + const { autoExtendRange, spectra } = inputOptions + const signalsOutOfRange: Record = {} - for (const exp in predictedSpectra) { - const experiment = exp as Experiment - if (!spectra[experiment]) continue - if (predictedSpectra[experiment]?.signals.length === 0) continue + for (const exp in predictedSpectra) { + const experiment = exp as Experiment + if (!spectra[experiment]) continue + if (predictedSpectra[experiment]?.signals.length === 0) continue - if (['carbon', 'proton'].includes(experiment)) { - const spectrum = predictedSpectra[experiment] as Prediction1D - const { signals, nucleus } = spectrum - const { from, to } = (inputOptions['1d'] as any)[nucleus] - const fromTo = getNewFromTo({ - deltas: signals.map((s) => s.delta), - from, - to, - nucleus, - autoExtendRange, - }) - setFromTo(inputOptions, nucleus, fromTo) - } else { - const { signals, nuclei } = predictedSpectra[experiment] as Prediction2D - for (const nucleus of nuclei) { - const axis = isProton(nucleus) ? 'x' : 'y' - const { from, to } = (inputOptions['1d'] as any)[nucleus] - const fromTo = getNewFromTo({ - deltas: signals.map((s) => s[axis].delta), - from, - to, - nucleus, - autoExtendRange, - }) - setFromTo(inputOptions, nucleus, fromTo) - } - } + if (['carbon', 'proton'].includes(experiment)) { + const spectrum = predictedSpectra[experiment] as Prediction1D + const { signals, nucleus } = spectrum + const { from, to } = (inputOptions['1d'] as any)[nucleus] + const fromTo = getNewFromTo({ + deltas: signals.map(s => s.delta), + from, + to, + nucleus, + autoExtendRange, + }) + setFromTo(inputOptions, nucleus, fromTo) + } else { + const { signals, nuclei } = predictedSpectra[experiment] as Prediction2D + for (const nucleus of nuclei) { + const axis = isProton(nucleus) ? 'x' : 'y' + const { from, to } = (inputOptions['1d'] as any)[nucleus] + const fromTo = getNewFromTo({ + deltas: signals.map(s => s[axis].delta), + from, + to, + nucleus, + autoExtendRange, + }) + setFromTo(inputOptions, nucleus, fromTo) + } } + } - for (const nucleus of ['1H', '13C']) { - if (signalsOutOfRange[nucleus]) { - const { from, to } = (inputOptions['1d'] as any)[nucleus] - if (autoExtendRange) { - console.log( - `There are ${nucleus} signals out of the range, it was extended to ${from}-${to}.`, - ) - } else { - console.log(`There are ${nucleus} signals out of the range.`) - } - } + for (const nucleus of ['1H', '13C']) { + if (signalsOutOfRange[nucleus]) { + const { from, to } = (inputOptions['1d'] as any)[nucleus] + if (autoExtendRange) { + console.log( + `There are ${nucleus} signals out of the range, it was extended to ${from}-${to}.` + ) + } else { + console.log(`There are ${nucleus} signals out of the range.`) + } } + } } - - function getNewFromTo(params: { - deltas: number[] - from: number - to: number - nucleus: string - autoExtendRange: boolean + deltas: number[] + from: number + to: number + nucleus: string + autoExtendRange: boolean }) { - const { deltas, nucleus, autoExtendRange } = params - let { from, to } = params - const { min, max } = xMinMaxValues(deltas) - const signalsOutOfRange = from > min || to < max + const { deltas, nucleus, autoExtendRange } = params + let { from, to } = params + const { min, max } = xMinMaxValues(deltas) + const signalsOutOfRange = from > min || to < max - if (autoExtendRange && signalsOutOfRange) { - const spread = isProton(nucleus) ? 0.2 : 2 - if (from > min) from = min - spread - if (to < max) to = max + spread - } + if (autoExtendRange && signalsOutOfRange) { + const spread = isProton(nucleus) ? 0.2 : 2 + if (from > min) from = min - spread + if (to < max) to = max + spread + } - return { from, to, signalsOutOfRange } -} \ No newline at end of file + return { from, to, signalsOutOfRange } +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generateName.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generateName.ts index 6c4f623..203a129 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generateName.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generateName.ts @@ -1,8 +1,8 @@ export function generateName( - name: string, - options: { frequency: number | number[]; experiment: string }, + name: string, + options: { frequency: number | number[]; experiment: string } ) { - const { frequency, experiment } = options - const freq = Array.isArray(frequency) ? frequency[0] : frequency - return name || `${experiment.toUpperCase()}_${freq}MHz` -} \ No newline at end of file + const { frequency, experiment } = options + const freq = Array.isArray(frequency) ? frequency[0] : frequency + return name || `${experiment.toUpperCase()}_${freq}MHz` +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated1DSpectrum.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated1DSpectrum.ts index fe62616..260c729 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated1DSpectrum.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated1DSpectrum.ts @@ -1,71 +1,76 @@ -import { getRelativeFrequency, mapRanges, signalsToRanges, signalsToXY, updateIntegralsRelativeValues } from "nmr-processing" -import { generateName } from "./generateName" -import { initiateDatum1D } from "../../../../parse/data/data1D/initiateDatum1D" -import { PredictionOptions } from "../nmrdb.engine" +import { + getRelativeFrequency, + mapRanges, + signalsToRanges, + signalsToXY, + updateIntegralsRelativeValues, +} from 'nmr-processing' +import { generateName } from './generateName.js' +import { initiateDatum1D } from '../../../../parse/data/data1D/initiateDatum1D.js' +import { PredictionOptions } from '../nmrdb.engine.js' export function generated1DSpectrum(params: { - options: PredictionOptions - spectrum: any - experiment: string - color: string + options: PredictionOptions + spectrum: any + experiment: string + color: string }) { - const { spectrum, options, experiment, color } = params - const { signals, joinedSignals, nucleus } = spectrum + const { spectrum, options, experiment, color } = params + const { signals, joinedSignals, nucleus } = spectrum - const { - name, - '1d': { nbPoints, lineWidth }, - frequency: freq, - } = options + const { + name, + '1d': { nbPoints, lineWidth }, + frequency: freq, + } = options - const SpectrumName = generateName(name, { frequency: freq, experiment }) - const frequency = getRelativeFrequency(nucleus, { - frequency: freq, - nucleus, - }) + const SpectrumName = generateName(name, { frequency: freq, experiment }) + const frequency = getRelativeFrequency(nucleus, { + frequency: freq, + nucleus, + }) - const { x, y } = signalsToXY(signals, { - ...(options['1d'] as any)[nucleus], - frequency, - nbPoints, - lineWidth, - }) + const { x, y } = signalsToXY(signals, { + ...(options['1d'] as any)[nucleus], + frequency, + nbPoints, + lineWidth, + }) - const first = x[0] ?? 0 - const last = x.at(-1) ?? 0 - const getFreqOffset = (freq: any) => { - return (first + last) * freq * 0.5 - } + const first = x[0] ?? 0 + const last = x.at(-1) ?? 0 + const getFreqOffset = (freq: any) => { + return (first + last) * freq * 0.5 + } - const datum = initiateDatum1D( - { - data: { x, im: null, re: y }, - display: { color }, - info: { - nucleus, - originFrequency: frequency, - baseFrequency: frequency, - frequencyOffset: Array.isArray(frequency) - ? frequency.map(getFreqOffset) - : getFreqOffset(frequency), - pulseSequence: 'prediction', - spectralWidth: Math.abs(first - last), - solvent: '', - experiment, - isFt: true, - name: SpectrumName, - title: SpectrumName, - }, - }, - {}, - ) + const datum = initiateDatum1D( + { + data: { x, im: null, re: y }, + display: { color }, + info: { + nucleus, + originFrequency: frequency, + baseFrequency: frequency, + frequencyOffset: Array.isArray(frequency) + ? frequency.map(getFreqOffset) + : getFreqOffset(frequency), + pulseSequence: 'prediction', + spectralWidth: Math.abs(first - last), + solvent: '', + experiment, + isFt: true, + name: SpectrumName, + title: SpectrumName, + }, + }, + {} + ) - datum.ranges.values = mapRanges( - signalsToRanges(joinedSignals, { frequency }), - datum, - ) - updateIntegralsRelativeValues(datum) + datum.ranges.values = mapRanges( + signalsToRanges(joinedSignals, { frequency }), + datum + ) + updateIntegralsRelativeValues(datum) - return datum + return datum } - diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated2DSpectrum.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated2DSpectrum.ts index c308f1b..81e2cd3 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated2DSpectrum.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/generated2DSpectrum.ts @@ -1,60 +1,64 @@ -import { calculateRelativeFrequency, PredictionBase2D, signals2DToZ } from "nmr-processing" -import { generateName } from "./generateName" -import { getSpectralWidth } from "./getSpectralWidth" -import { initiateDatum2D } from "../../../../parse/data/data2d/initiateDatum2D" -import { adjustAlpha } from "../../../../utilities/adjustAlpha" -import { mapZones } from "./mapZones" -import { PredictionOptions } from "../nmrdb.engine" +import { + calculateRelativeFrequency, + PredictionBase2D, + signals2DToZ, +} from 'nmr-processing' +import { generateName } from './generateName.js' +import { getSpectralWidth } from './getSpectralWidth.js' +import { initiateDatum2D } from '../../../../parse/data/data2d/initiateDatum2D.js' +import { adjustAlpha } from '../../../../utilities/adjustAlpha.js' +import { mapZones } from './mapZones.js' +import { PredictionOptions } from '../nmrdb.engine.js' export function generated2DSpectrum(params: { - options: PredictionOptions - spectrum: PredictionBase2D - experiment: string - color: string + options: PredictionOptions + spectrum: PredictionBase2D + experiment: string + color: string }) { - const { spectrum, options, experiment, color } = params - const { signals, zones, nuclei } = spectrum - const xOption = (options['1d'] as any)[nuclei[0]] - const yOption = (options['1d'] as any)[nuclei[1]] + const { spectrum, options, experiment, color } = params + const { signals, zones, nuclei } = spectrum + const xOption = (options['1d'] as any)[nuclei[0]] + const yOption = (options['1d'] as any)[nuclei[1]] - const width = nuclei[0] === nuclei[1] ? 0.02 : { x: 0.02, y: 0.2133 } - const frequency = calculateRelativeFrequency(nuclei, options.frequency) + const width = nuclei[0] === nuclei[1] ? 0.02 : { x: 0.02, y: 0.2133 } + const frequency = calculateRelativeFrequency(nuclei, options.frequency) - const minMaxContent = signals2DToZ(signals, { - from: { x: xOption.from, y: yOption.from }, - to: { x: xOption.to, y: yOption.to }, - nbPoints: { - x: options['2d'].nbPoints.x, - y: options['2d'].nbPoints.y, - }, - width, - factor: 3, - }) + const minMaxContent = signals2DToZ(signals, { + from: { x: xOption.from, y: yOption.from }, + to: { x: xOption.to, y: yOption.to }, + nbPoints: { + x: options['2d'].nbPoints.x, + y: options['2d'].nbPoints.y, + }, + width, + factor: 3, + }) - const SpectrumName = generateName(options.name, { - frequency, - experiment, - }) + const SpectrumName = generateName(options.name, { + frequency, + experiment, + }) - const spectralWidth = getSpectralWidth(experiment, options) - const datum = initiateDatum2D({ - data: { rr: { ...minMaxContent, noise: 0.01 } }, - display: { - positiveColor: color, - negativeColor: adjustAlpha(color, 40), - }, - info: { - name: SpectrumName, - title: SpectrumName, - nucleus: nuclei, - originFrequency: frequency, - baseFrequency: frequency, - pulseSequence: 'prediction', - spectralWidth, - experiment, - }, - }) + const spectralWidth = getSpectralWidth(experiment, options) + const datum = initiateDatum2D({ + data: { rr: { ...minMaxContent, noise: 0.01 } }, + display: { + positiveColor: color, + negativeColor: adjustAlpha(color, 40), + }, + info: { + name: SpectrumName, + title: SpectrumName, + nucleus: nuclei, + originFrequency: frequency, + baseFrequency: frequency, + pulseSequence: 'prediction', + spectralWidth, + experiment, + }, + }) - datum.zones.values = mapZones(zones) - return datum + datum.zones.values = mapZones(zones) + return datum } diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/getSpectralWidth.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/getSpectralWidth.ts index 305fbf5..2894621 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/getSpectralWidth.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/getSpectralWidth.ts @@ -1,23 +1,26 @@ -import { PredictionOptions } from "../nmrdb.engine" +import { PredictionOptions } from '../nmrdb.engine.js' -export function getSpectralWidth(experiment: string, options: PredictionOptions) { - const formTo = options['1d'] +export function getSpectralWidth( + experiment: string, + options: PredictionOptions +) { + const formTo = options['1d'] - switch (experiment) { - case 'cosy': { - const { from, to } = formTo['1H'] - const diff = to - from - return [diff, diff] - } - case 'hsqc': - case 'hmbc': { - const proton = formTo['1H'] - const carbon = formTo['13C'] - const protonDiff = proton.to - proton.from - const carbonDiff = carbon.to - carbon.from - return [protonDiff, carbonDiff] - } - default: - return [] + switch (experiment) { + case 'cosy': { + const { from, to } = formTo['1H'] + const diff = to - from + return [diff, diff] } -} \ No newline at end of file + case 'hsqc': + case 'hmbc': { + const proton = formTo['1H'] + const carbon = formTo['13C'] + const protonDiff = proton.to - proton.from + const carbonDiff = carbon.to - carbon.from + return [protonDiff, carbonDiff] + } + default: + return [] + } +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/mapZones.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/mapZones.ts index 338d790..5230de5 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/mapZones.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/core/mapZones.ts @@ -1,30 +1,28 @@ -import { Peak2D, Signal2D, Zone } from "@zakodium/nmr-types" -import { NMRZone } from "nmr-processing" +import { Peak2D, Signal2D, Zone } from '@zakodium/nmr-types' +import { NMRZone } from 'nmr-processing' export function mapZones(zones: NMRZone[]): Zone[] { - return zones.map((zone): Zone => { - const { signals, ...resZone } = zone - const newSignals = signals.map((signal): Signal2D => { - const { x, y, id, peaks, kind, ...resSignal } = signal - return { - ...resSignal, - id: id || crypto.randomUUID(), - kind: kind || 'signal', - x: { ...x, originalDelta: x.delta || 0 }, - y: { ...y, originalDelta: y.delta || 0 }, - peaks: peaks?.map( - (peak): Peak2D => ({ - ...peak, - id: peak.id || crypto.randomUUID(), - }), - ), - } - }) - return { - ...resZone, - id: crypto.randomUUID(), - signals: newSignals, - kind: 'signal', - } + return zones.map((zone): Zone => { + const { signals, ...resZone } = zone + const newSignals = signals.map((signal): Signal2D => { + const { x, y, id, peaks, kind, ...resSignal } = signal + return { + ...resSignal, + id: id || crypto.randomUUID(), + kind: kind || 'signal', + x: { ...x, originalDelta: x.delta || 0 }, + y: { ...y, originalDelta: y.delta || 0 }, + peaks: peaks?.map((peak): Peak2D => ({ + ...peak, + id: peak.id || crypto.randomUUID(), + })), + } }) -} \ No newline at end of file + return { + ...resZone, + id: crypto.randomUUID(), + signals: newSignals, + kind: 'signal', + } + }) +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/nmrdb.engine.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/nmrdb.engine.ts index ea7b90b..f8bdc3b 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrdb/nmrdb.engine.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrdb/nmrdb.engine.ts @@ -1,37 +1,37 @@ import type { Options } from 'yargs' import type { Spectrum } from '@zakodium/nmrium-core' import type { - Predicted, - PredictionBase1D, - PredictionBase2D, - PredictionOptionsByExperiment, + Predicted, + PredictionBase1D, + PredictionBase2D, + PredictionOptionsByExperiment, } from 'nmr-processing' import { predict } from 'nmr-processing' import { Molecule } from 'openchemlib' -import { defineEngine } from '../registry' -import type { Experiment } from '../base' -import { checkFromTo } from './core/checkFromTo' -import { generated1DSpectrum } from './core/generated1DSpectrum' -import { generated2DSpectrum } from './core/generated2DSpectrum' +import { defineEngine } from '../registry.js' +import type { Experiment } from '../base.js' +import { checkFromTo } from './core/checkFromTo.js' +import { generated1DSpectrum } from './core/generated1DSpectrum.js' +import { generated2DSpectrum } from './core/generated2DSpectrum.js' export type PredictedSpectraResult = Partial< - Record + Record > export interface PredictionOptions { - name: string - frequency: number - '1d': { - '1H': { from: number; to: number } - '13C': { from: number; to: number } - nbPoints: number - lineWidth: number - } - '2d': { - nbPoints: { x: number; y: number } - } - autoExtendRange: boolean - spectra: Record + name: string + frequency: number + '1d': { + '1H': { from: number; to: number } + '13C': { from: number; to: number } + nbPoints: number + lineWidth: number + } + '2d': { + nbPoints: { x: number; y: number } + } + autoExtendRange: boolean + spectra: Record } // ============================================================================ @@ -39,8 +39,8 @@ export interface PredictionOptions { // ============================================================================ const experimentToPredictKey: Record = { - proton: 'H', - carbon: 'C', + proton: 'H', + carbon: 'C', } // ============================================================================ @@ -48,61 +48,66 @@ const experimentToPredictKey: Record = { // ============================================================================ export async function predictSpectra( - molfile: string, - spectra: Record, + molfile: string, + spectra: Record ): Promise { - const molecule = Molecule.fromMolfile(molfile) + const molecule = Molecule.fromMolfile(molfile) - const predictOptions: Record = {} - for (const [experiment, enabled] of Object.entries(spectra)) { - if (!enabled) continue - const key = experimentToPredictKey[experiment] ?? experiment - predictOptions[key] = {} - } + const predictOptions: Record = {} + for (const [experiment, enabled] of Object.entries(spectra)) { + if (!enabled) continue + const key = experimentToPredictKey[experiment] ?? experiment + predictOptions[key] = {} + } - return predict(molecule, { predictOptions }) + return predict(molecule, { predictOptions }) } export function generateSpectra( - predictedSpectra: PredictedSpectraResult, - options: PredictionOptions, - color: string, + predictedSpectra: PredictedSpectraResult, + options: PredictionOptions, + color: string ): Spectrum[] { - const clonedOptions = structuredClone(options) - checkFromTo(predictedSpectra, clonedOptions) - - const spectra: Spectrum[] = [] - - for (const [experiment, spectrum] of Object.entries(predictedSpectra)) { - if (!clonedOptions.spectra[experiment as Experiment]) continue - - switch (experiment) { - case 'proton': - case 'carbon': { - spectra.push( - generated1DSpectrum({ spectrum, options: clonedOptions, experiment, color }), - ) - break - } - case 'cosy': - case 'hsqc': - case 'hmbc': { - spectra.push( - generated2DSpectrum({ - spectrum: spectrum as PredictionBase2D, - options: clonedOptions, - experiment, - color, - }), - ) - break - } - default: - break - } + const clonedOptions = structuredClone(options) + checkFromTo(predictedSpectra, clonedOptions) + + const spectra: Spectrum[] = [] + + for (const [experiment, spectrum] of Object.entries(predictedSpectra)) { + if (!clonedOptions.spectra[experiment as Experiment]) continue + + switch (experiment) { + case 'proton': + case 'carbon': { + spectra.push( + generated1DSpectrum({ + spectrum, + options: clonedOptions, + experiment, + color, + }) + ) + break + } + case 'cosy': + case 'hsqc': + case 'hmbc': { + spectra.push( + generated2DSpectrum({ + spectrum: spectrum as PredictionBase2D, + options: clonedOptions, + experiment, + color, + }) + ) + break + } + default: + break } + } - return spectra + return spectra } // ============================================================================ @@ -110,118 +115,118 @@ export function generateSpectra( // ============================================================================ export const nmrdbEngine = defineEngine({ - id: 'nmrdb.org', - name: 'NMRDB.org', - description: 'NMRDB.org prediction engine with 1D and 2D NMR support', - supportedSpectra: ['proton', 'carbon', 'cosy', 'hmbc', 'hsqc'], - - options: { - name: { - type: 'string', - description: 'Compound name', - default: '', - }, - frequency: { - type: 'number', - description: 'NMR frequency (MHz)', - default: 400, - }, - protonFrom: { - type: 'number', - description: 'Proton (1H) from in ppm', - default: -1, - }, - protonTo: { - type: 'number', - description: 'Proton (1H) to in ppm', - default: 12, - }, - carbonFrom: { - type: 'number', - description: 'Carbon (13C) from in ppm', - default: -5, - }, - carbonTo: { - type: 'number', - description: 'Carbon (13C) to in ppm', - default: 220, - }, - nbPoints1d: { - type: 'number', - description: '1D number of points', - default: 2 ** 17, - }, - lineWidth: { - type: 'number', - description: '1D line width', - default: 1, - }, - nbPoints2dX: { - type: 'number', - description: '2D spectrum X-axis points', - default: 1024, + id: 'nmrdb.org', + name: 'NMRDB.org', + description: 'NMRDB.org prediction engine with 1D and 2D NMR support', + supportedSpectra: ['proton', 'carbon', 'cosy', 'hmbc', 'hsqc'], + + options: { + name: { + type: 'string', + description: 'Compound name', + default: '', + }, + frequency: { + type: 'number', + description: 'NMR frequency (MHz)', + default: 400, + }, + protonFrom: { + type: 'number', + description: 'Proton (1H) from in ppm', + default: -1, + }, + protonTo: { + type: 'number', + description: 'Proton (1H) to in ppm', + default: 12, + }, + carbonFrom: { + type: 'number', + description: 'Carbon (13C) from in ppm', + default: -5, + }, + carbonTo: { + type: 'number', + description: 'Carbon (13C) to in ppm', + default: 220, + }, + nbPoints1d: { + type: 'number', + description: '1D number of points', + default: 2 ** 17, + }, + lineWidth: { + type: 'number', + description: '1D line width', + default: 1, + }, + nbPoints2dX: { + type: 'number', + description: '2D spectrum X-axis points', + default: 1024, + }, + nbPoints2dY: { + type: 'number', + description: '2D spectrum Y-axis points', + default: 1024, + }, + autoExtendRange: { + type: 'boolean', + description: 'Auto extend range', + default: true, + }, + } as Record, + + requiredOptions: [], + + buildPayloadOptions(argv: Record): PredictionOptions { + const spectraObj: Record = { + carbon: false, + proton: false, + cosy: false, + hmbc: false, + hsqc: false, + } + + for (const experiment of argv.spectra as string[]) { + spectraObj[experiment as Experiment] = true + } + + return { + name: (argv.name as string) || '', + frequency: (argv.frequency as number) || 400, + '1d': { + '1H': { + from: (argv.protonFrom as number) ?? -1, + to: (argv.protonTo as number) ?? 12, }, - nbPoints2dY: { - type: 'number', - description: '2D spectrum Y-axis points', - default: 1024, + '13C': { + from: (argv.carbonFrom as number) ?? -5, + to: (argv.carbonTo as number) ?? 220, }, - autoExtendRange: { - type: 'boolean', - description: 'Auto extend range', - default: true, + nbPoints: (argv.nbPoints1d as number) || 2 ** 17, + lineWidth: (argv.lineWidth as number) || 1, + }, + '2d': { + nbPoints: { + x: (argv.nbPoints2dX as number) || 1024, + y: (argv.nbPoints2dY as number) || 1024, }, - } as Record, - - requiredOptions: [], - - buildPayloadOptions(argv: Record): PredictionOptions { - const spectraObj: Record = { - carbon: false, - proton: false, - cosy: false, - hmbc: false, - hsqc: false, - } - - for (const experiment of argv.spectra as string[]) { - spectraObj[experiment as Experiment] = true - } - - return { - name: (argv.name as string) || '', - frequency: (argv.frequency as number) || 400, - '1d': { - '1H': { - from: (argv.protonFrom as number) ?? -1, - to: (argv.protonTo as number) ?? 12, - }, - '13C': { - from: (argv.carbonFrom as number) ?? -5, - to: (argv.carbonTo as number) ?? 220, - }, - nbPoints: (argv.nbPoints1d as number) || 2 ** 17, - lineWidth: (argv.lineWidth as number) || 1, - }, - '2d': { - nbPoints: { - x: (argv.nbPoints2dX as number) || 1024, - y: (argv.nbPoints2dY as number) || 1024, - }, - }, - spectra: spectraObj, - autoExtendRange: argv.autoExtendRange !== false, - } - }, + }, + spectra: spectraObj, + autoExtendRange: argv.autoExtendRange !== false, + } + }, - async predict(structure, options) { - const predictionOptions = this.buildPayloadOptions(options) + async predict(structure, options) { + const predictionOptions = this.buildPayloadOptions(options) - const { spectra } = await predictSpectra( - structure, - predictionOptions.spectra, - ) + const { spectra } = await predictSpectra( + structure, + predictionOptions.spectra + ) - return generateSpectra(spectra, predictionOptions, 'red') - }, -}) \ No newline at end of file + return generateSpectra(spectra, predictionOptions, 'red') + }, +}) diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/extractInfoFromSpectra.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/extractInfoFromSpectra.ts index 56a666d..babd9fa 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/extractInfoFromSpectra.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/extractInfoFromSpectra.ts @@ -1,15 +1,13 @@ -import { Experiment } from "../../base"; -import { spectraTypeMap, SpectraTypeMapItem } from "./spectraTypeMap"; - - +import { Experiment } from '../../base.js' +import { spectraTypeMap, SpectraTypeMapItem } from './spectraTypeMap.js' export function extractInfoFromSpectra(spectra: Experiment[]) { - const info: SpectraTypeMapItem[] = []; - for (const experiment of spectra) { - const data = spectraTypeMap[experiment]; - if (!data) continue; - - info.push(data) - } - return info; -} \ No newline at end of file + const info: SpectraTypeMapItem[] = [] + for (const experiment of spectra) { + const data = spectraTypeMap[experiment] + if (!data) continue + + info.push(data) + } + return info +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/getNucleusFromSpectra.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/getNucleusFromSpectra.ts index c23bffb..7bb8d98 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/getNucleusFromSpectra.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/getNucleusFromSpectra.ts @@ -1,13 +1,13 @@ -import { Experiment } from "../../base" -import { spectraTypeMap } from "./spectraTypeMap" +import { Experiment } from '../../base.js' +import { spectraTypeMap } from './spectraTypeMap.js' export function getNucleusFromSpectra(spectra: Experiment[]): string { - const nuclei = new Set() - for (const spectrum of spectra) { - const entry = spectraTypeMap[spectrum] - if (entry) { - nuclei.add(entry.nucleus) - } + const nuclei = new Set() + for (const spectrum of spectra) { + const entry = spectraTypeMap[spectrum] + if (entry) { + nuclei.add(entry.nucleus) } - return nuclei.size > 0 ? [...nuclei].join(',') : '1H' -} \ No newline at end of file + } + return nuclei.size > 0 ? [...nuclei].join(',') : '1H' +} diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/spectraTypeMap.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/spectraTypeMap.ts index 729345f..27c65ee 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/spectraTypeMap.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/core/spectraTypeMap.ts @@ -1,12 +1,11 @@ -import { Experiment } from "../../base"; +import { Experiment } from '../../base.js' export interface SpectraTypeMapItem { - type: string; nucleus: string + type: string + nucleus: string } export const spectraTypeMap: Partial> = { - proton: { type: 'nmr;1H;1d', nucleus: '1H' }, - carbon: - - { type: 'nmr;13C;1d', nucleus: '13C' }, + proton: { type: 'nmr;1H;1d', nucleus: '1H' }, + carbon: { type: 'nmr;13C;1d', nucleus: '13C' }, } diff --git a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/nmrshift.engine.ts b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/nmrshift.engine.ts index 46788eb..8e22e8e 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/nmrshift/nmrshift.engine.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/nmrshift/nmrshift.engine.ts @@ -1,44 +1,47 @@ import type { Options } from 'yargs' import type { Spectrum } from '@zakodium/nmrium-core' import https from 'https' -import axios from 'axios' - -import { defineEngine } from '../registry' -import type { Experiment } from '../base' -import type { GenerateSpectrumOptions, ShiftsItem } from './core/generatePredictedSpectrumData' -import { generatePredictedSpectrumData } from './core/generatePredictedSpectrumData' -import { extractInfoFromSpectra } from './core/extractInfoFromSpectra' -import type { SpectraTypeMapItem } from './core/spectraTypeMap' +import axios, { type AxiosResponse } from 'axios' + +import { defineEngine } from '../registry.js' +import type { Experiment } from '../base.js' +import type { + GenerateSpectrumOptions, + ShiftsItem, +} from './core/generatePredictedSpectrumData.js' +import { generatePredictedSpectrumData } from './core/generatePredictedSpectrumData.js' +import { extractInfoFromSpectra } from './core/extractInfoFromSpectra.js' +import type { SpectraTypeMapItem } from './core/spectraTypeMap.js' interface NMRShiftPayload { - id: number - type: string - shifts: string - solvent: string + id: number + type: string + shifts: string + solvent: string } interface PredictionResponseItem { - id: number - type: string - statistics: { - accept: number - warning: number - reject: number - missing: number - total: number - } - shifts: ShiftsItem[] + id: number + type: string + statistics: { + accept: number + warning: number + reject: number + missing: number + total: number + } + shifts: ShiftsItem[] } interface PredictionResponse { - result: PredictionResponseItem[] + result: PredictionResponseItem[] } interface NMRShiftOptions { - id: number - shifts: string - solvent: string - spectra: Experiment[] + id: number + shifts: string + solvent: string + spectra: Experiment[] } type PredictionArgs = NMRShiftOptions & GenerateSpectrumOptions @@ -48,123 +51,132 @@ type PredictionArgs = NMRShiftOptions & GenerateSpectrumOptions // ============================================================================ function getBaseUrl(): string { - const url = process.env['NMR_PREDICTION_URL'] - if (!url) { - throw new Error('Environment variable NMR_PREDICTION_URL is not defined.') - } - try { - new URL(url) - } catch { - throw new Error(`Invalid URL in NMR_PREDICTION_URL: "${url}"`) - } - return url + const url = process.env['NMR_PREDICTION_URL'] + if (!url) { + throw new Error('Environment variable NMR_PREDICTION_URL is not defined.') + } + try { + new URL(url) + } catch { + throw new Error(`Invalid URL in NMR_PREDICTION_URL: "${url}"`) + } + return url } async function callPredict( - structure: string, - options: { - id: number - shifts: string - solvent: string - experiments: SpectraTypeMapItem[] - }, -): Promise[]> { - const url = getBaseUrl() - const { id, shifts, solvent, experiments } = options - - const httpsAgent = new https.Agent({ rejectUnauthorized: false }) - - const requests = experiments.map((experimentInfo) => { - const payload: NMRShiftPayload = { - id, - type: experimentInfo.type, - shifts, - solvent, - } - - return axios.post(url, { - inputs: [payload], - moltxt: structure, - }, { - headers: { 'Content-Type': 'application/json' }, - httpsAgent, - }) - }) - - return Promise.all(requests) + structure: string, + options: { + id: number + shifts: string + solvent: string + experiments: SpectraTypeMapItem[] + } +): Promise[]> { + const url = getBaseUrl() + const { id, shifts, solvent, experiments } = options + + const httpsAgent = new https.Agent({ rejectUnauthorized: false }) + + const requests = experiments.map(experimentInfo => { + const payload: NMRShiftPayload = { + id, + type: experimentInfo.type, + shifts, + solvent, + } + + return axios.post( + url, + { + inputs: [payload], + moltxt: structure, + }, + { + headers: { 'Content-Type': 'application/json' }, + httpsAgent, + } + ) + }) + + return Promise.all(requests) } async function predictNMR( - structure: string, - options: PredictionArgs, + structure: string, + options: PredictionArgs ): Promise { - const { - id = 1, - shifts = '1', - solvent = 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', + const { + id = 1, + shifts = '1', + solvent = 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', + from, + to, + nbPoints = 1024, + frequency = 400, + lineWidth = 1, + tolerance = 0.001, + peakShape = 'lorentzian', + spectra, + } = options + + // Derive experiments (type + nucleus) from the --spectra list + const experiments = extractInfoFromSpectra(spectra) + + if (experiments.length === 0) { + throw new Error( + `No supported experiments found for spectra [${spectra.join(', ')}]. ` + + `Supported: proton, carbon.` + ) + } + + const results = await callPredict(structure, { + id, + shifts, + solvent, + experiments, + }) + + const outputSpectra: Spectrum[] = [] + + for (let i = 0; i < results.length; i++) { + const response: PredictionResponse = results[i].data + const experimentInfo = experiments[i] + + for (const item of response.result) { + const data = generatePredictedSpectrumData(item.shifts, { from, to, - nbPoints = 1024, - frequency = 400, - lineWidth = 1, - tolerance = 0.001, - peakShape = 'lorentzian', - spectra, - } = options - - // Derive experiments (type + nucleus) from the --spectra list - const experiments = extractInfoFromSpectra(spectra) - - if (experiments.length === 0) { - throw new Error( - `No supported experiments found for spectra [${spectra.join(', ')}]. ` + - `Supported: proton, carbon.`, - ) - } - - const results = await callPredict(structure, { id, shifts, solvent, experiments }) - - const outputSpectra: Spectrum[] = [] - - for (let i = 0; i < results.length; i++) { - const response: PredictionResponse = results[i].data - const experimentInfo = experiments[i] - - for (const item of response.result) { - const data = generatePredictedSpectrumData(item.shifts, { - from, - to, - nbPoints, - lineWidth, - frequency, - tolerance, - peakShape, - }) - - if (!data) continue - - const name = crypto.randomUUID() - - outputSpectra.push({ - id: crypto.randomUUID(), - data, - info: { - isFid: false, - isComplex: false, - dimension: 1, - originFrequency: frequency, - baseFrequency: frequency, - pulseSequence: '', - solvent, - isFt: true, - name, - nucleus: experimentInfo.nucleus, - }, - } as unknown as Spectrum) - } + nbPoints, + lineWidth, + frequency, + tolerance, + peakShape, + }) + + if (!data) continue + + const name = crypto.randomUUID() + + outputSpectra.push({ + id: crypto.randomUUID(), + data, + info: { + isFid: false, + isComplex: false, + dimension: 1, + originFrequency: frequency, + baseFrequency: frequency, + pulseSequence: '', + solvent, + isFt: true, + name, + nucleus: experimentInfo.nucleus, + }, + } as unknown as Spectrum) } + } - return outputSpectra + return outputSpectra } // ============================================================================ @@ -172,93 +184,94 @@ async function predictNMR( // ============================================================================ const SOLVENT_CHOICES = [ - 'Any', - 'Chloroform-D1 (CDCl3)', - 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', - 'Methanol-D4 (CD3OD)', - 'Deuteriumoxide (D2O)', - 'Acetone-D6 ((CD3)2CO)', - 'TETRACHLORO-METHANE (CCl4)', - 'Pyridin-D5 (C5D5N)', - 'Benzene-D6 (C6D6)', - 'neat', - 'Tetrahydrofuran-D8 (THF-D8, C4D4O)', + 'Any', + 'Chloroform-D1 (CDCl3)', + 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', + 'Methanol-D4 (CD3OD)', + 'Deuteriumoxide (D2O)', + 'Acetone-D6 ((CD3)2CO)', + 'TETRACHLORO-METHANE (CCl4)', + 'Pyridin-D5 (C5D5N)', + 'Benzene-D6 (C6D6)', + 'neat', + 'Tetrahydrofuran-D8 (THF-D8, C4D4O)', ] as const export const nmrshiftEngine = defineEngine({ - id: 'nmrshift', - name: 'NMRShift', - description: 'NMRShift prediction engine', - - supportedSpectra: ['proton', 'carbon'], - - options: { - id: { - alias: 'i', - type: 'number', - description: 'Input ID', - default: 1, - }, - shifts: { - type: 'string', - description: 'Chemical shifts', - default: '1', - }, - solvent: { - type: 'string', - description: 'NMR solvent', - default: 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', - choices: SOLVENT_CHOICES, - }, - from: { - type: 'number', - description: 'From in (ppm) for spectrum generation', - }, - to: { - type: 'number', - description: 'To in (ppm) for spectrum generation', - }, - nbPoints: { - type: 'number', - description: 'Number of points for spectrum generation', - default: 1024, - }, - lineWidth: { - type: 'number', - description: 'Line width for spectrum generation', - default: 1, - }, - frequency: { - type: 'number', - description: 'NMR frequency (MHz) for spectrum generation', - default: 400, - }, - tolerance: { - type: 'number', - description: 'Tolerance to group peaks with close shift', - default: 0.001, - }, - peakShape: { - alias: 'ps', - type: 'string', - description: 'Peak shape algorithm', - default: 'lorentzian', - choices: ['gaussian', 'lorentzian'], - }, - } as Record, + id: 'nmrshift', + name: 'NMRShift', + description: 'NMRShift prediction engine', + + supportedSpectra: ['proton', 'carbon'], + + options: { + id: { + alias: 'i', + type: 'number', + description: 'Input ID', + default: 1, + }, + shifts: { + type: 'string', + description: 'Chemical shifts', + default: '1', + }, + solvent: { + type: 'string', + description: 'NMR solvent', + default: 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', + choices: SOLVENT_CHOICES, + }, + from: { + type: 'number', + description: 'From in (ppm) for spectrum generation', + }, + to: { + type: 'number', + description: 'To in (ppm) for spectrum generation', + }, + nbPoints: { + type: 'number', + description: 'Number of points for spectrum generation', + default: 1024, + }, + lineWidth: { + type: 'number', + description: 'Line width for spectrum generation', + default: 1, + }, + frequency: { + type: 'number', + description: 'NMR frequency (MHz) for spectrum generation', + default: 400, + }, + tolerance: { + type: 'number', + description: 'Tolerance to group peaks with close shift', + default: 0.001, + }, + peakShape: { + alias: 'ps', + type: 'string', + description: 'Peak shape algorithm', + default: 'lorentzian', + choices: ['gaussian', 'lorentzian'], + }, + } as Record, - requiredOptions: ['solvent'], + requiredOptions: ['solvent'], - buildPayloadOptions(argv: Record): NMRShiftOptions { - return { - id: (argv.id as number) ?? 1, - shifts: (argv.shifts as string) ?? '1', - solvent: (argv.solvent as string) ?? 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', - spectra: argv.spectra as Experiment[], - } - }, + buildPayloadOptions(argv: Record): NMRShiftOptions { + return { + id: (argv.id as number) ?? 1, + shifts: (argv.shifts as string) ?? '1', + solvent: + (argv.solvent as string) ?? 'Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)', + spectra: argv.spectra as Experiment[], + } + }, - async predict(structure, options) { - return predictNMR(structure, options as unknown as PredictionArgs) - }, -}) \ No newline at end of file + async predict(structure, options) { + return predictNMR(structure, options as unknown as PredictionArgs) + }, +}) diff --git a/app/scripts/nmr-cli/src/prediction/engines/registry.ts b/app/scripts/nmr-cli/src/prediction/engines/registry.ts index c6ea442..d5c1eb5 100644 --- a/app/scripts/nmr-cli/src/prediction/engines/registry.ts +++ b/app/scripts/nmr-cli/src/prediction/engines/registry.ts @@ -1,50 +1,50 @@ -import type { Engine } from './base' +import type { Engine } from './base.js' /** * Auto-discovered engine registry * Engines are automatically registered when imported */ class EngineRegistry { - private engines = new Map() - - /** - * Register an engine - * Called automatically when engine files are imported - */ - register(engine: Engine): void { - if (this.engines.has(engine.id)) { - console.warn(`Engine ${engine.id} is already registered, overwriting...`) - } - this.engines.set(engine.id, engine) - } - - /** - * Get an engine by ID - */ - get(id: string): Engine | undefined { - return this.engines.get(id) - } - - /** - * Get all registered engines - */ - getAll(): Engine[] { - return Array.from(this.engines.values()) - } - - /** - * Get all engine IDs - */ - getIds(): string[] { - return Array.from(this.engines.keys()) - } - - /** - * Check if an engine exists - */ - has(id: string): boolean { - return this.engines.has(id) + private engines = new Map() + + /** + * Register an engine + * Called automatically when engine files are imported + */ + register(engine: Engine): void { + if (this.engines.has(engine.id)) { + console.warn(`Engine ${engine.id} is already registered, overwriting...`) } + this.engines.set(engine.id, engine) + } + + /** + * Get an engine by ID + */ + get(id: string): Engine | undefined { + return this.engines.get(id) + } + + /** + * Get all registered engines + */ + getAll(): Engine[] { + return Array.from(this.engines.values()) + } + + /** + * Get all engine IDs + */ + getIds(): string[] { + return Array.from(this.engines.keys()) + } + + /** + * Check if an engine exists + */ + has(id: string): boolean { + return this.engines.has(id) + } } // Singleton instance @@ -55,6 +55,6 @@ export const engineRegistry = new EngineRegistry() * Just call this at the bottom of your engine file! */ export function defineEngine(engine: Engine): Engine { - engineRegistry.register(engine) - return engine -} \ No newline at end of file + engineRegistry.register(engine) + return engine +} diff --git a/app/scripts/nmr-cli/src/prediction/index.ts b/app/scripts/nmr-cli/src/prediction/index.ts index 4041a77..1ea0c99 100644 --- a/app/scripts/nmr-cli/src/prediction/index.ts +++ b/app/scripts/nmr-cli/src/prediction/index.ts @@ -1,8 +1,8 @@ import { Argv, CommandModule } from 'yargs' import { readFileSync, existsSync, writeFileSync } from 'fs' import { CURRENT_EXPORT_VERSION } from '@zakodium/nmrium-core' -import { engineRegistry } from './engines' -import type { Experiment } from './engines/base' +import { engineRegistry } from './engines/index.js' +import type { Experiment } from './engines/base.js' // ============================================================================ // STRUCTURE INPUT HANDLING @@ -15,30 +15,30 @@ import type { Experiment } from './engines/base' * 3. Inline MOL content (as fallback) */ function resolveStructureInput(options: { - structure?: string - stdin?: boolean - file?: string + structure?: string + stdin?: boolean + file?: string }): string { - // Priority 1: Explicit file flag - if (options.file) { - if (!existsSync(options.file)) { - throw new Error(`File not found: ${options.file}`) - } - return readFileSync(options.file, 'utf-8') + // Priority 1: Explicit file flag + if (options.file) { + if (!existsSync(options.file)) { + throw new Error(`File not found: ${options.file}`) } + return readFileSync(options.file, 'utf-8') + } - // Priority 2: Explicit stdin flag - if (options.stdin) { - return readStdinSync() - } + // Priority 2: Explicit stdin flag + if (options.stdin) { + return readStdinSync() + } - // Priority 3: Structure argument (-s flag) - ALWAYS treat as inline content - if (options.structure) { - // Do NOT check existsSync here - just return it as inline MOL content - return options.structure.trimEnd().replaceAll(/\\n/g, '\n') - } + // Priority 3: Structure argument (-s flag) - ALWAYS treat as inline content + if (options.structure) { + // Do NOT check existsSync here - just return it as inline MOL content + return options.structure.trimEnd().replaceAll(/\\n/g, '\n') + } - throw new Error('No structure input provided. Use --file, --stdin, or -s') + throw new Error('No structure input provided. Use --file, --stdin, or -s') } /** @@ -46,12 +46,12 @@ function resolveStructureInput(options: { * This works because yargs has already parsed args, so stdin is available */ function readStdinSync(): string { - try { - // File descriptor 0 is stdin - return readFileSync(0, 'utf-8') - } catch (error) { - throw new Error('Failed to read from stdin. Is data being piped?') - } + try { + // File descriptor 0 is stdin + return readFileSync(0, 'utf-8') + } catch (error) { + throw new Error('Failed to read from stdin. Is data being piped?') + } } // ============================================================================ @@ -59,44 +59,44 @@ function readStdinSync(): string { // ============================================================================ const commonOptions = { - engine: { - alias: 'e', - type: 'string', - description: 'Prediction engine', - demandOption: true, - choices: engineRegistry.getIds(), - }, - spectra: { - type: 'array', - description: 'Spectra types to predict', - demandOption: true, - choices: ['proton', 'carbon', 'cosy', 'hsqc', 'hmbc'], - }, - // Option 1: File path (most explicit) - file: { - alias: 'f', - type: 'string', - description: 'Path to MOL file', - conflicts: ['stdin', 'structure'], - }, - // Option 2: Stdin flag - stdin: { - type: 'boolean', - description: 'Read structure from stdin', - conflicts: ['file', 'structure'], - }, - // Option 3: Structure argument (inline MOL content only) - structure: { - alias: 's', - type: 'string', - description: 'Inline MOL content (use --file for file paths)', - conflicts: ['file', 'stdin'], - }, - output: { - alias: 'o', - type: 'string', - description: 'Output file path (default: stdout)', - } + engine: { + alias: 'e', + type: 'string', + description: 'Prediction engine', + demandOption: true, + choices: engineRegistry.getIds(), + }, + spectra: { + type: 'array', + description: 'Spectra types to predict', + demandOption: true, + choices: ['proton', 'carbon', 'cosy', 'hsqc', 'hmbc'], + }, + // Option 1: File path (most explicit) + file: { + alias: 'f', + type: 'string', + description: 'Path to MOL file', + conflicts: ['stdin', 'structure'], + }, + // Option 2: Stdin flag + stdin: { + type: 'boolean', + description: 'Read structure from stdin', + conflicts: ['file', 'structure'], + }, + // Option 3: Structure argument (inline MOL content only) + structure: { + alias: 's', + type: 'string', + description: 'Inline MOL content (use --file for file paths)', + conflicts: ['file', 'stdin'], + }, + output: { + alias: 'o', + type: 'string', + description: 'Output file path (default: stdout)', + }, } as const // ============================================================================ @@ -104,40 +104,42 @@ const commonOptions = { // ============================================================================ function validateEngineOptions(argv: Record): void { - const engineId = argv.engine as string - const spectra = argv.spectra as string[] - const engine = engineRegistry.get(engineId) + const engineId = argv.engine as string + const spectra = argv.spectra as string[] + const engine = engineRegistry.get(engineId) - if (!engine) { - const available = engineRegistry.getIds().join(', ') - throw new Error(`Unknown engine "${engineId}". Available engines: ${available}`) - } - - const unsupportedSpectra = spectra.filter( - (s) => !engine.supportedSpectra.includes(s as Experiment), + if (!engine) { + const available = engineRegistry.getIds().join(', ') + throw new Error( + `Unknown engine "${engineId}". Available engines: ${available}` ) + } - if (unsupportedSpectra.length > 0) { - throw new Error( - `Engine "${engineId}" does not support: ${unsupportedSpectra.join(', ')}.\n` + - `Supported spectra: ${engine.supportedSpectra.join(', ')}`, - ) - } + const unsupportedSpectra = spectra.filter( + s => !engine.supportedSpectra.includes(s as Experiment) + ) - const missing = engine.requiredOptions.filter((opt) => !argv[opt]) - if (missing.length > 0) { - throw new Error( - `Engine "${engineId}" requires: ${missing.join(', ')}\n` + - `Usage: --${missing.join(' --')}`, - ) - } + if (unsupportedSpectra.length > 0) { + throw new Error( + `Engine "${engineId}" does not support: ${unsupportedSpectra.join(', ')}.\n` + + `Supported spectra: ${engine.supportedSpectra.join(', ')}` + ) + } - if (engine.validate) { - const result = engine.validate(argv) - if (result !== true) { - throw new Error(result) - } + const missing = engine.requiredOptions.filter(opt => !argv[opt]) + if (missing.length > 0) { + throw new Error( + `Engine "${engineId}" requires: ${missing.join(', ')}\n` + + `Usage: --${missing.join(' --')}` + ) + } + + if (engine.validate) { + const result = engine.validate(argv) + if (result !== true) { + throw new Error(result) } + } } // ============================================================================ @@ -145,107 +147,118 @@ function validateEngineOptions(argv: Record): void { // ============================================================================ async function predictNMR(options: Record): Promise { - const engineId = options.engine as string - const engine = engineRegistry.get(engineId)! - - // Resolve structure from input - const structure = resolveStructureInput({ - structure: options.structure as string | undefined, - stdin: options.stdin as boolean | undefined, - file: options.file as string | undefined, - }) - - // DEBUG LOGGING - console.error('[DEBUG] Received structure:', structure ? `${structure.length} chars` : 'undefined') - console.error('[DEBUG] Structure type:', typeof structure) - console.error('[DEBUG] Structure preview:', structure?.substring(0, 100)) - - // Validate structure is not empty - if (!structure || !structure.trim()) { - throw new Error('Structure input is empty or undefined') - } + const engineId = options.engine as string + const engine = engineRegistry.get(engineId)! - // Run prediction - const spectraResults = await engine.predict(structure, options) + // Resolve structure from input + const structure = resolveStructureInput({ + structure: options.structure as string | undefined, + stdin: options.stdin as boolean | undefined, + file: options.file as string | undefined, + }) - // Build NMRium output - const nmrium = { - data: { spectra: spectraResults }, - version: CURRENT_EXPORT_VERSION, - } - const output = JSON.stringify(nmrium) + // DEBUG LOGGING + console.error( + '[DEBUG] Received structure:', + structure ? `${structure.length} chars` : 'undefined' + ) + console.error('[DEBUG] Structure type:', typeof structure) + console.error('[DEBUG] Structure preview:', structure?.substring(0, 100)) - // Handle output destination - if (options.output) { - const outputPath = options.output as string - writeFileSync(outputPath, output, 'utf-8') - console.error(`Results written to ${outputPath}`) - } + // Validate structure is not empty + if (!structure || !structure.trim()) { + throw new Error('Structure input is empty or undefined') + } + + // Run prediction + const spectraResults = await engine.predict(structure, options) + + // Build NMRium output + const nmrium = { + data: { spectra: spectraResults }, + version: CURRENT_EXPORT_VERSION, + } + const output = JSON.stringify(nmrium) + + // Handle output destination + if (options.output) { + const outputPath = options.output as string + writeFileSync(outputPath, output, 'utf-8') + console.error(`Results written to ${outputPath}`) + } - return output + return output } // ============================================================================ // COMMAND MODULE // ============================================================================ -export const parsePredictionCommand: CommandModule<{}, Record> = { - command: ['predict', 'p'], - describe: 'Predict NMR spectrum from mol text', - builder: (yargs: Argv): Argv => { - let y = yargs.options(commonOptions) +export const parsePredictionCommand: CommandModule< + {}, + Record +> = { + command: ['predict', 'p'], + describe: 'Predict NMR spectrum from mol text', + builder: (yargs: Argv): Argv => { + let y = yargs.options(commonOptions) - for (const engine of engineRegistry.getAll()) { - y = y.options(engine.options) as Argv - } + for (const engine of engineRegistry.getAll()) { + y = y.options(engine.options) as Argv + } - return y - .check((argv) => { - // Ensure at least one input method is provided - if (!argv.file && !argv.stdin && !argv.structure) { - throw new Error( - 'Must provide structure input via --file, --stdin, or -s' - ) - } - return true - }) - .example( - '$0 predict -e myengine --spectra proton -f molecule.mol', - 'Predict from file path' - ) - .example( - '$0 predict -e myengine --spectra proton --stdin < molecule.mol', - 'Predict from stdin (redirect)' - ) - .example( - 'cat molecule.mol | $0 predict -e myengine --spectra proton --stdin', - 'Predict from stdin (pipe)' - ) - .example( - '$0 predict -e myengine --spectra proton -s "\\n MOL content..."', - 'Predict using -s with inline MOL content' - ) - .example( - '$0 predict -e myengine --spectra proton -f mol.mol -o results.json', - 'Save output to file' - ) - }, - handler: async (argv) => { - // DEBUG: See ALL arguments - console.error('[DEBUG] Full argv:', JSON.stringify(argv, null, 2)) - console.error('[DEBUG] argv.structure exists?', argv.structure !== undefined) - console.error('[DEBUG] argv.engine:', argv.engine) - console.error('[DEBUG] argv.spectra:', argv.spectra) - - try { - validateEngineOptions(argv) - const output = await predictNMR(argv) - console.log(output) - } catch (error) { - console.error('Error:', error instanceof Error ? error.message : String(error)) - console.error('Error stack:', error instanceof Error ? error.stack : '') - process.exit(1) + return y + .check(argv => { + // Ensure at least one input method is provided + if (!argv.file && !argv.stdin && !argv.structure) { + throw new Error( + 'Must provide structure input via --file, --stdin, or -s' + ) } + return true + }) + .example( + '$0 predict -e myengine --spectra proton -f molecule.mol', + 'Predict from file path' + ) + .example( + '$0 predict -e myengine --spectra proton --stdin < molecule.mol', + 'Predict from stdin (redirect)' + ) + .example( + 'cat molecule.mol | $0 predict -e myengine --spectra proton --stdin', + 'Predict from stdin (pipe)' + ) + .example( + '$0 predict -e myengine --spectra proton -s "\\n MOL content..."', + 'Predict using -s with inline MOL content' + ) + .example( + '$0 predict -e myengine --spectra proton -f mol.mol -o results.json', + 'Save output to file' + ) + }, + handler: async argv => { + // DEBUG: See ALL arguments + console.error('[DEBUG] Full argv:', JSON.stringify(argv, null, 2)) + console.error( + '[DEBUG] argv.structure exists?', + argv.structure !== undefined + ) + console.error('[DEBUG] argv.engine:', argv.engine) + console.error('[DEBUG] argv.spectra:', argv.spectra) - }, -} \ No newline at end of file + try { + validateEngineOptions(argv) + const output = await predictNMR(argv) + console.log(output) + } catch (error) { + console.error( + 'Error:', + error instanceof Error ? error.message : String(error) + ) + console.error('Error stack:', error instanceof Error ? error.stack : '') + process.exit(1) + } + }, +} diff --git a/app/scripts/nmr-cli/src/publication-string.ts b/app/scripts/nmr-cli/src/publication-string.ts index e8a719a..3726043 100644 --- a/app/scripts/nmr-cli/src/publication-string.ts +++ b/app/scripts/nmr-cli/src/publication-string.ts @@ -1,9 +1,6 @@ -import { - resurrect, - rangesToXY, -} from 'nmr-processing' +import { resurrect, rangesToXY } from 'nmr-processing' import { CURRENT_EXPORT_VERSION } from '@zakodium/nmrium-core' -import { castToArray } from './utilities/castToArray' +import { castToArray } from './utilities/castToArray.js' import { NMRRange } from '@zakodium/nmr-types' interface Info { @@ -12,10 +9,7 @@ interface Info { name: string } -function generateSpectrumFromRanges( - ranges: NMRRange[], - info: Info -) { +function generateSpectrumFromRanges(ranges: NMRRange[], info: Info) { const { nucleus, solvent, name = null } = info const frequency = 400 diff --git a/app/scripts/nmr-cli/src/utilities/adjustAlpha.ts b/app/scripts/nmr-cli/src/utilities/adjustAlpha.ts index 9223381..e7fb6c1 100644 --- a/app/scripts/nmr-cli/src/utilities/adjustAlpha.ts +++ b/app/scripts/nmr-cli/src/utilities/adjustAlpha.ts @@ -1,10 +1,10 @@ function percentToHex(p: number): string { - const percent = Math.max(0, Math.min(100, p)); - const intValue = Math.round((percent / 100) * 255); - const hexValue = intValue.toString(16); - return percent === 100 ? '' : hexValue.padStart(2, '0'); + const percent = Math.max(0, Math.min(100, p)) + const intValue = Math.round((percent / 100) * 255) + const hexValue = intValue.toString(16) + return percent === 100 ? '' : hexValue.padStart(2, '0') } export function adjustAlpha(color: string, factor: number): string { - return color + percentToHex(factor); -} \ No newline at end of file + return color + percentToHex(factor) +} diff --git a/app/scripts/nmr-cli/src/utilities/isProton.ts b/app/scripts/nmr-cli/src/utilities/isProton.ts index ad8c5e9..00f3e6e 100644 --- a/app/scripts/nmr-cli/src/utilities/isProton.ts +++ b/app/scripts/nmr-cli/src/utilities/isProton.ts @@ -1,3 +1,3 @@ export function isProton(nucleus: string) { - return nucleus === '1H'; + return nucleus === '1H' }