diff --git a/app/scripts/nmr-cli/.gitignore b/app/scripts/nmr-cli/.gitignore new file mode 100644 index 0000000..b38db2f --- /dev/null +++ b/app/scripts/nmr-cli/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +build/ diff --git a/app/scripts/nmr-cli/Dockerfile b/app/scripts/nmr-cli/Dockerfile index a6b0393..afc252d 100644 --- a/app/scripts/nmr-cli/Dockerfile +++ b/app/scripts/nmr-cli/Dockerfile @@ -18,8 +18,10 @@ RUN npm install COPY . ./ +RUN npm run build + #install the nmr-cli as a global package -# for example, nmr-cli -u https://cheminfo.github.io/bruker-data-test/data/zipped/aspirin-1h.zip +# for example, nmr-cli parse-spectra -u https://cheminfo.github.io/bruker-data-test/data/zipped/aspirin-1h.zip RUN npm install . -g diff --git a/app/scripts/nmr-cli/bin/index.js b/app/scripts/nmr-cli/bin/index.js deleted file mode 100755 index a8a895b..0000000 --- a/app/scripts/nmr-cli/bin/index.js +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env node -const { join, isAbsolute } = require("path"); -const yargs = require("yargs"); -const loader = require("nmr-load-save"); -const fileUtils = require("filelist-utils"); -const playwright = require('playwright'); - -const usageMessage = "Usage: nmr-cli -u or -p -s" - - -/** - * How to Use the Command Line Tool: - * Example 1: Process spectra files from a URL - * Usage: nmr-cli -u https://example.com/file.zip - * ------------------------------------------------------------------------- - * Example 2: process a spectra files from a directory - * Usage: nmr-cli -p /path/to/directory - * ------------------------------------------------------------------------- - * you could also combine the above examples with an optional parameter to capturing a snapshot using the -s option - * - */ - -const options = yargs - .usage(usageMessage) - .option("u", { alias: "url", describe: "File URL", type: "string", nargs: 1 }) - .option("p", { alias: "path", describe: "Directory path", type: "string", nargs: 1 }) - .option("s", { alias: "capture-snapshot", describe: "Capture snapshot", type: "boolean" }).showHelpOnFail(); - - - - -function generateNMRiumURL() { - const baseURL = process.env['BASE_NMRIUM_URL']; - const url = new URL(baseURL) - url.searchParams.append('workspace', "embedded") - return url.toString() -} - -async function captureSpectraViewAsBase64(nmriumState) { - const { data: { spectra }, version } = nmriumState; - const browser = await playwright.chromium.launch() - const context = await browser.newContext(playwright.devices['Desktop Chrome HiDPI']) - const page = await context.newPage() - - const url = generateNMRiumURL() - - await page.goto(url) - - await page.locator('text=Loading').waitFor({ state: 'hidden' }); - - let snapshots = [] - - for (const spectrum of spectra || []) { - const spectrumObject = { - version, - data: { - spectra: [{ ...spectrum }], - } - - } - - // convert typed array to array - const stringObject = JSON.stringify(spectrumObject, (key, value) => { - return ArrayBuffer.isView(value) ? Array.from(value) : value - }) - - // load the spectrum into NMRium using the custom event - await page.evaluate( - ` - window.postMessage({ type: "nmr-wrapper:load", data:{data: ${stringObject},type:"nmrium"}}, '*'); - ` - ) - - //wait for NMRium process and load spectra - await page.locator('text=Loading').waitFor({ state: 'hidden' }); - - // take a snapshot for the spectrum - try { - const snapshot = await page.locator('#nmrSVG .container').screenshot() - - snapshots.push({ - image: snapshot.toString('base64'), - id: spectrum.id, - }) - } catch (e) { - console.log(e) - } - } - - await context.close() - await browser.close() - - return snapshots; -} - -async function loadSpectrumFromURL(url, enableSnapshot = false) { - const { pathname: relativePath, origin: baseURL } = new URL(url); - const source = { - entries: [ - { - relativePath, - } - ], - baseURL - }; - const fileCollection = await fileUtils.fileCollectionFromWebSource(source, {}); - - const { - nmriumState: { data, version }, - } = await loader.read(fileCollection); - - let images = [] - - if (enableSnapshot) { - images = await captureSpectraViewAsBase64({ data, version }); - } - - - return { data, version, images }; -} - - -async function loadSpectrumFromFilePath(path, enableSnapshot = false) { - const dirPath = isAbsolute(path) ? path : join(process.cwd(), path) - - const fileCollection = await fileUtils.fileCollectionFromPath(dirPath, {}); - - const { - nmriumState: { data, version } - } = await loader.read(fileCollection); - - let images = [] - - if (enableSnapshot) { - images = await captureSpectraViewAsBase64({ data, version }); - } - - - return { data, version, images }; -} - - -const parameters = options.argv; - -if (parameters.u && parameters.p) { - options.showHelp(); -} else { - - if (parameters.u) { - loadSpectrumFromURL(parameters.u, parameters.s).then((result) => { - console.log(JSON.stringify(result)) - }) - - } - - if (parameters.p) { - loadSpectrumFromFilePath(parameters.p, parameters.s).then((result) => { - console.log(JSON.stringify(result)) - }) - } - -} - - - - - diff --git a/app/scripts/nmr-cli/package-lock.json b/app/scripts/nmr-cli/package-lock.json index 2497fcf..20fb3ce 100644 --- a/app/scripts/nmr-cli/package-lock.json +++ b/app/scripts/nmr-cli/package-lock.json @@ -10,12 +10,56 @@ "license": "ISC", "dependencies": { "filelist-utils": "^1.10.2", - "nmr-load-save": "^0.23.11", + "nmr-load-save": "^0.24.0", + "nmr-processing": "^11.7.0", "playwright": "^1.40.1", "yargs": "^17.7.2" }, "bin": { "nmr-cli": "bin/index.js" + }, + "devDependencies": { + "@types/node": "^20.11.5", + "@types/yargs": "^17.0.32", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/@lukeed/csprng": { @@ -37,6 +81,30 @@ "node": ">=8" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.196", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz", @@ -50,6 +118,51 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "20.11.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz", + "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -72,6 +185,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -100,15 +219,15 @@ "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" }, "node_modules/brukerconverter": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/brukerconverter/-/brukerconverter-6.3.2.tgz", - "integrity": "sha512-+sR1s3uOuZSkxxW06WmHn0X10HFVngRG1a+/4FUZmWypUEFtD+W/7OyNBK3if/1Kx6hX2oJrkL1xwCNH/srwKg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/brukerconverter/-/brukerconverter-6.3.4.tgz", + "integrity": "sha512-WoIAyD4ANOnWuemOJFUZuwt+OamliivwidfXhdMGbhXCI5ZB4rlHEP0NycuOsbOvsahJ1vtsHpsowSVMh8Lovw==", "dependencies": { "cheminfo-types": "^1.5.0", "filelist-utils": "^1.8.0", "iobuffer": "^5.3.2", "is-any-array": "^2.0.0", - "jcampconverter": "^9.1.0", + "jcampconverter": "^9.5.1", "ml-spectra-processing": "^12.0.0" } }, @@ -175,6 +294,12 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -201,6 +326,15 @@ "node": ">=0.4.0" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dynamic-typing": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dynamic-typing/-/dynamic-typing-1.0.0.tgz", @@ -357,9 +491,9 @@ } }, "node_modules/jcampconverter": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/jcampconverter/-/jcampconverter-9.5.0.tgz", - "integrity": "sha512-xsuZXcUd+YkF0Rlt0IZa1+4XbMYq/fR6+gewwDJd5M17y2+SbjrgESKZH73+GokzLC7H1KTS0/l5/7cQFFLgiA==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/jcampconverter/-/jcampconverter-9.6.0.tgz", + "integrity": "sha512-rLkyJxnGM3e4KD5NfPfHtFuk4lMnvHgmpfxvUiRlJDLizIFgiNgsiFAgQTqey0OY6eJQAM381TmxSb3DkEPNSg==", "dependencies": { "cheminfo-types": "^1.7.2", "dynamic-typing": "^1.0.0", @@ -426,6 +560,12 @@ "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/median-quickselect": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/median-quickselect/-/median-quickselect-1.0.1.tgz", @@ -731,24 +871,24 @@ } }, "node_modules/nmr-load-save": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-0.23.11.tgz", - "integrity": "sha512-tvZDBfiZYy7xBUdx7MVVoQ3M3DzhsEKZCgkjkVk0tGVNcy1q9C1WYT48nSJXhgTMNslfH8GH9f2ojSTFnHoUDQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-0.24.0.tgz", + "integrity": "sha512-08Kouo4DjkJj6CPr4fgXO82oLZzexTqRqZOCtP2TDor+N7wae7yo8MtwotrPLY4VQDHoiRIy6A96KEuWviimZQ==", "dependencies": { "@lukeed/uuid": "^2.0.1", "@types/lodash.merge": "^4.6.7", - "brukerconverter": "^6.3.2", + "brukerconverter": "^6.3.3", "cheminfo-types": "^1.7.2", "convert-to-jcamp": "^5.4.9", "filelist-utils": "^1.10.2", "gyromagnetic-ratio": "^1.1.0", "is-any-array": "^2.0.1", - "jcampconverter": "^9.5.0", + "jcampconverter": "^9.6.0", "jeolconverter": "^1.0.2", "lodash.merge": "^4.6.2", "ml-spectra-processing": "^12.8.0", "nmr-correlation": "^2.3.3", - "nmr-processing": "^11.6.1", + "nmr-processing": "^11.7.0", "nmredata": "^0.9.9", "openchemlib": "^8.7.0", "openchemlib-utils": "^5.6.0", @@ -757,25 +897,25 @@ } }, "node_modules/nmr-processing": { - "version": "11.6.1", - "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-11.6.1.tgz", - "integrity": "sha512-NA6uoFbqkqFdrEoEKf3KLeh22opI7torSBi017n4Sqc/jis5/A7MpgunmHVxFYo09BDBIwKM4YvWur86NKXBUw==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-11.7.0.tgz", + "integrity": "sha512-wws8YO5M9UOE+HFxLUtcRJR/nH8InrKCWvX2Hejo8xeuA1oODrkW4TauyfPzXDK1fmqJ82U9ou78H+55alqShw==", "dependencies": { "@lukeed/uuid": "^2.0.1", "binary-search": "^1.3.6", "cross-fetch": "^4.0.0", "form-data": "^4.0.0", - "gyromagnetic-ratio": "^1.1.0", + "gyromagnetic-ratio": "^1.1.1", "is-any-array": "^2.0.1", "linear-sum-assignment": "^1.0.5", "lodash.omit": "^4.5.0", - "ml-airpls": "^1.0.2", + "ml-airpls": "^1.0.3", "ml-baseline-correction-regression": "^1.0.2", "ml-direct": "^0.1.3", "ml-gsd": "^12.1.3", "ml-hclust": "^3.1.0", "ml-levenberg-marquardt": "^4.1.3", - "ml-matrix": "^6.10.7", + "ml-matrix": "^6.11.0", "ml-matrix-convolution": "^1.0.0", "ml-matrix-peaks-finder": "^1.0.0", "ml-peak-shape-generator": "^4.1.2", @@ -786,7 +926,7 @@ "ml-tree-set": "^0.1.1", "nmr-correlation": "^2.3.3", "numeral": "^2.0.6", - "openchemlib-utils": "^5.4.0", + "openchemlib-utils": "^5.6.1", "spectrum-generator": "^8.0.8" } }, @@ -999,11 +1139,79 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/varian-converter": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/varian-converter/-/varian-converter-0.3.3.tgz", @@ -1076,6 +1284,15 @@ "engines": { "node": ">=12" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } } -} \ No newline at end of file +} diff --git a/app/scripts/nmr-cli/package.json b/app/scripts/nmr-cli/package.json index 83792f0..5b7df59 100644 --- a/app/scripts/nmr-cli/package.json +++ b/app/scripts/nmr-cli/package.json @@ -2,20 +2,29 @@ "name": "test-node", "version": "1.0.0", "description": "", - "main": "bin/index.js", + "main": "./build/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc", + "start": "node build/index.js", + "dev": "ts-node src/index.ts" }, "keywords": [], "author": "", "license": "ISC", "bin": { - "nmr-cli": "./bin/index.js" + "nmr-cli": "./build/index.js" }, "dependencies": { "filelist-utils": "^1.10.2", - "nmr-load-save": "^0.23.11", + "nmr-load-save": "^0.24.0", + "nmr-processing": "^11.7.0", "playwright": "^1.40.1", "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/node": "^20.11.5", + "@types/yargs": "^17.0.32", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" } } \ No newline at end of file diff --git a/app/scripts/nmr-cli/src/index.ts b/app/scripts/nmr-cli/src/index.ts new file mode 100755 index 0000000..a66dec1 --- /dev/null +++ b/app/scripts/nmr-cli/src/index.ts @@ -0,0 +1,108 @@ +#!/usr/bin/env node +import yargs, { type Argv, type CommandModule, type Options, } from "yargs"; +import { loadSpectrumFromURL, loadSpectrumFromFilePath } from "./prase-spectra"; +import { generateSpectrumFromPublicationString } from "./publication-string"; + + +const usageMessage = ` +Usage: nmr-cli [options] + +Commands: + parse-spectra Parse a spectra file to NMRium file + parse-publication-string resurrect spectrum from the publication string + +Options for 'parse-spectra' command: + -u, --url File URL + -p, --path Directory path + -s, --capture-snapshot Capture snapshot + +Arguments for 'parse-publication-string' command: + publicationString Publication string + +Examples: + nmr-cli parse-spectra -u file-url -s // Process spectra files from a URL and capture an image for the spectra + nmr-cli parse-spectra -p directory-path -s // process a spectra files from a directory and capture an image for the spectra + nmr-cli parse-spectra -u file-url // Process spectra files from a URL + nmr-cli parse-spectra -p directory-path // Process spectra files from a directory + nmr-cli parse-publication-string "your publication string" +`; + +interface FileOptionsArgs { + u?: string; + p?: string; + s?: boolean; +} + + +// Define options for parsing a spectra file +const fileOptions: { [key in keyof FileOptionsArgs]: Options } = { + u: { + alias: 'url', + describe: 'File URL', + type: 'string', + nargs: 1, + }, + p: { + alias: 'path', + describe: 'Directory path', + type: 'string', + nargs: 1, + }, + s: { + alias: 'capture-snapshot', + describe: 'Capture snapshot', + type: 'boolean', + }, +} as const; + + +const parseFileCommand: CommandModule<{}, FileOptionsArgs> = { + command: ['parse-spectra', 'ps'], + describe: 'Parse a spectra file to NMRium file', + builder: (yargs) => { + return yargs.options(fileOptions).conflicts('u', 'p') as Argv; + }, + handler: (argv) => { + // Handle parsing the spectra file logic based on argv options + if (argv?.u) { + loadSpectrumFromURL(argv.u, argv.s).then((result) => { + console.log(JSON.stringify(result)) + }) + + } + + if (argv?.p) { + loadSpectrumFromFilePath(argv.p, argv.s).then((result) => { + console.log(JSON.stringify(result)) + }) + } + }, +}; + +// Define the parse publication string command +const parsePublicationCommand: CommandModule = { + command: ['parse-publication-string', 'pps'], + describe: 'Parse a publication string', + handler: (argv) => { + const publicationString = argv._[1]; + // Handle parsing publication string + if (typeof publicationString == "string") { + const nmriumObject = generateSpectrumFromPublicationString(publicationString); + + + console.log(JSON.stringify(nmriumObject)); + } + }, +}; + +yargs + .usage(usageMessage) + .command(parseFileCommand) + .command(parsePublicationCommand) + .showHelpOnFail(true) + .help() + .parse(); + + + + diff --git a/app/scripts/nmr-cli/src/prase-spectra.ts b/app/scripts/nmr-cli/src/prase-spectra.ts new file mode 100644 index 0000000..b107ecc --- /dev/null +++ b/app/scripts/nmr-cli/src/prase-spectra.ts @@ -0,0 +1,129 @@ +import { join, isAbsolute } from "path"; +import { type NmriumState, read } from "nmr-load-save"; +import { fileCollectionFromWebSource, fileCollectionFromPath } from "filelist-utils"; +import playwright from 'playwright'; + + +interface Snapshot { + image: string, + id: string; +} + + +function generateNMRiumURL() { + const baseURL = process.env['BASE_NMRIUM_URL'] || ''; + const url = new URL(baseURL) + url.searchParams.append('workspace', "embedded") + return url.toString() +} + + +async function captureSpectraViewAsBase64(nmriumState: Partial) { + const { data: { spectra } = { spectra: [] }, version } = nmriumState; + const browser = await playwright.chromium.launch() + const context = await browser.newContext(playwright.devices['Desktop Chrome HiDPI']) + const page = await context.newPage() + + const url = generateNMRiumURL() + + await page.goto(url) + + await page.locator('text=Loading').waitFor({ state: 'hidden' }); + + let snapshots: Snapshot[] = [] + + for (const spectrum of spectra || []) { + const spectrumObject = { + version, + data: { + spectra: [{ ...spectrum }], + } + + } + + // convert typed array to array + const stringObject = JSON.stringify(spectrumObject, (key, value: unknown) => { + return ArrayBuffer.isView(value) ? Array.from(value as unknown as Iterable) : value + }) + + // load the spectrum into NMRium using the custom event + await page.evaluate( + ` + window.postMessage({ type: "nmr-wrapper:load", data:{data: ${stringObject},type:"nmrium"}}, '*'); + ` + ) + + //wait for NMRium process and load spectra + await page.locator('text=Loading').waitFor({ state: 'hidden' }); + + // take a snapshot for the spectrum + try { + const snapshot = await page.locator('#nmrSVG .container').screenshot() + + snapshots.push({ + image: snapshot.toString('base64'), + id: spectrum.id, + }) + } catch (e) { + console.log(e) + } + } + + await context.close() + await browser.close() + + return snapshots; +} + +async function loadSpectrumFromURL(url: string, enableSnapshot = false) { + const { pathname: relativePath, origin: baseURL } = new URL(url); + const source = { + entries: [ + { + relativePath, + } + ], + baseURL + }; + const fileCollection = await fileCollectionFromWebSource(source, {}); + + const { + nmriumState: { data, version }, + } = await read(fileCollection); + + let images: Snapshot[] = [] + + if (enableSnapshot) { + images = await captureSpectraViewAsBase64({ data, version }); + } + + + return { data, version, images }; +} + + +async function loadSpectrumFromFilePath(path: string, enableSnapshot = false) { + const dirPath = isAbsolute(path) ? path : join(process.cwd(), path) + + const fileCollection = await fileCollectionFromPath(dirPath, {}); + + const { + nmriumState: { data, version } + } = await read(fileCollection); + + let images: Snapshot[] = [] + + if (enableSnapshot) { + images = await captureSpectraViewAsBase64({ data, version }); + } + + + return { data, version, images }; +} + + +export { + loadSpectrumFromFilePath, + loadSpectrumFromURL +}; + diff --git a/app/scripts/nmr-cli/src/publication-string.ts b/app/scripts/nmr-cli/src/publication-string.ts new file mode 100644 index 0000000..f423894 --- /dev/null +++ b/app/scripts/nmr-cli/src/publication-string.ts @@ -0,0 +1,73 @@ +import { resurrect, rangesToXY, type NMRRange } from 'nmr-processing'; +import { v4 } from '@lukeed/uuid'; +import { CURRENT_EXPORT_VERSION } from 'nmr-load-save'; +import { castToArray } from './utilities/castToArray' + + +interface Info { + nucleus: string; solvent: string; name: string +} + +function generateSpectrumFromRanges( + ranges: NMRRange[], + info: Info, +) { + const { nucleus, solvent, name = null } = info; + + const frequency = 400; + try { + const { x, y } = rangesToXY(ranges, { + nucleus, + frequency, + nbPoints: 2 ** 17, + }); + + const info = { + isFid: false, + isComplex: false, + dimension: 1, + nucleus, + originFrequency: frequency, + baseFrequency: frequency, + pulseSequence: '', + solvent, + isFt: true, + name, + }; + + const spectrum = + { + id: v4(), + data: { x: castToArray(x), im: undefined, re: castToArray(y) }, + info, + ranges: { + values: ranges, options: { + sum: 100, + isSumConstant: false, + sumAuto: false + } + }, + }; + + return { data: { spectra: [spectrum] }, version: CURRENT_EXPORT_VERSION }; + } catch (error) { + console.log(error); + } +} + + +function generateSpectrumFromPublicationString( + publicationString: string +) { + const { + ranges, + experiment: { nucleus, solvent }, + parts, + } = resurrect(publicationString); + return generateSpectrumFromRanges( + ranges, + { nucleus, solvent, name: parts[0] }, + ); +} + +export { generateSpectrumFromPublicationString } diff --git a/app/scripts/nmr-cli/src/utilities/castToArray.ts b/app/scripts/nmr-cli/src/utilities/castToArray.ts new file mode 100644 index 0000000..6cd3a48 --- /dev/null +++ b/app/scripts/nmr-cli/src/utilities/castToArray.ts @@ -0,0 +1,3 @@ +export function castToArray(value: Float64Array | number[]): number[] { + return ArrayBuffer.isView(value) ? Array.from(value) : value +} \ No newline at end of file diff --git a/app/scripts/nmr-cli/tsconfig.json b/app/scripts/nmr-cli/tsconfig.json new file mode 100644 index 0000000..0f9b561 --- /dev/null +++ b/app/scripts/nmr-cli/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "outDir": "./build", + "skipLibCheck": true, + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file