nmr-processing
Advanced tools
Comparing version 1.5.2 to 2.0.0
# Changelog | ||
## [2.0.0](https://www.github.com/cheminfo/nmr-processing/compare/v1.5.2...v2.0.0) (2021-07-23) | ||
### ⚠ BREAKING CHANGES | ||
* use plural name diaIDs instead diaID | ||
* use plural names signals and js instead of signal and j in ranges | ||
* use integration instead of integral | ||
### Features | ||
* **prediction:** prediction 2D ([#68](https://www.github.com/cheminfo/nmr-processing/issues/68)) ([fed091b](https://www.github.com/cheminfo/nmr-processing/commit/fed091b48e21cf29624d7322ddc952d590f16495)) | ||
* predictor returns molecule ([9d7ba24](https://www.github.com/cheminfo/nmr-processing/commit/9d7ba24225aa0fb9a3c4d5fb25f55032c081bdb3)) | ||
* use integration instead of integral ([80ccc25](https://www.github.com/cheminfo/nmr-processing/commit/80ccc25ec21fcb6df5130af40aaf03ce3ea73f17)) | ||
* use plural name diaIDs instead diaID ([8cbcefc](https://www.github.com/cheminfo/nmr-processing/commit/8cbcefc3c3225039cf4f236e2ef908233ccc9ad6)) | ||
* use plural names signals and js instead of signal and j in ranges ([2328a13](https://www.github.com/cheminfo/nmr-processing/commit/2328a13f4084b00927c19ff3ec012c692a00d22a)) | ||
### Bug Fixes | ||
* database in json format ([9adb6bf](https://www.github.com/cheminfo/nmr-processing/commit/9adb6bfec39eb22b4f238e538cb972462204e2e0)) | ||
### [1.5.2](https://www.github.com/cheminfo/nmr-processing/compare/v1.5.1...v1.5.2) (2021-07-22) | ||
@@ -4,0 +26,0 @@ |
{ | ||
"name": "nmr-processing", | ||
"version": "1.5.2", | ||
"version": "2.0.0", | ||
"description": "", | ||
@@ -47,3 +47,3 @@ "main": "lib/index.js", | ||
"cheminfo-build": "^1.1.11", | ||
"eslint": "^7.30.0", | ||
"eslint": "^7.31.0", | ||
"eslint-config-cheminfo": "^5.2.4", | ||
@@ -58,3 +58,3 @@ "esm": "^3.2.25", | ||
"prettier": "^2.3.2", | ||
"rollup": "^2.53.2" | ||
"rollup": "^2.53.3" | ||
}, | ||
@@ -79,3 +79,3 @@ "dependencies": { | ||
"ml-sparse-matrix": "^2.1.0", | ||
"ml-spectra-processing": "^6.7.1", | ||
"ml-spectra-processing": "^6.8.0", | ||
"openchemlib-utils": "^1.2.0", | ||
@@ -82,0 +82,0 @@ "spectrum-generator": "^4.7.1" |
@@ -15,2 +15,5 @@ /** | ||
export * from './prediction/predictCarbon'; | ||
export * from './prediction/predictCOSY'; | ||
export * from './prediction/predictHSQC'; | ||
export * from './prediction/predictHMBC'; | ||
export * from './xyz/xyzAutoPeaksPicking'; | ||
@@ -17,0 +20,0 @@ export * from './constants/gyromagneticRatio'; |
@@ -8,7 +8,7 @@ // import { Ranges } from 'spectra-data-ranges'; | ||
/** | ||
* This function clustering peaks and calculate the integral value for each range from the peak list returned from extractPeaks function. | ||
* This function clustering peaks and calculate the integration value for each range from the peak list returned from extractPeaks function. | ||
* @param {Object} data - spectra data | ||
* @param {Array} peakList - nmr signals | ||
* @param {Object} [options={}] - options object with some parameter for GSD, detectSignal functions. | ||
* @param {Number} [options.integrationSum=100] - Number of hydrogens or some number to normalize the integral data. If it's zero return the absolute integral value | ||
* @param {Number} [options.integrationSum=100] - Number of hydrogens or some number to normalize the integration data. If it's zero return the absolute integration value | ||
* @param {String} [options.integralType='sum'] - option to chose between approx area with peaks or the sum of the points of given range ('sum', 'peaks') | ||
@@ -133,4 +133,4 @@ * @param {Number} [options.frequencyCluster=16] - distance limit to clustering peaks. | ||
to: signal.integralData.to, | ||
integral: signal.integralData.value, | ||
signal: [ | ||
integration: signal.integralData.value, | ||
signals: [ | ||
{ | ||
@@ -143,9 +143,9 @@ kind: signal.kind || 'signal', | ||
if (keepPeaks) { | ||
ranges[i].signal[0].peak = signal.peaks; | ||
ranges[i].signals[0].peak = signal.peaks; | ||
} | ||
if (signal.nmrJs) { | ||
ranges[i].signal[0].j = signal.nmrJs; | ||
ranges[i].signals[0].js = signal.nmrJs; | ||
} | ||
if (!signal.asymmetric || signal.multiplicity === 'm') { | ||
ranges[i].signal[0].delta = signal.delta1; | ||
ranges[i].signals[0].delta = signal.delta1; | ||
} | ||
@@ -164,3 +164,3 @@ } | ||
* @param {object} [options = {}] | ||
* @param {number} [options.integrationSum='100'] - Number of hydrogens or some number to normalize the integration data, If it's zero return the absolute integral value | ||
* @param {number} [options.integrationSum='100'] - Number of hydrogens or some number to normalize the integration data, If it's zero return the absolute integration value | ||
* @param {string} [options.integralType='sum'] - option to chose between approx area with peaks or the sum of the points of given range | ||
@@ -238,3 +238,3 @@ * @param {number} [options.frequencyCluster=16] - distance limit to clustering the peaks. | ||
peaks = signals[i].peaks; | ||
let integral = signals[i].integralData; | ||
let integration = signals[i].integralData; | ||
let chemicalShift = 0; | ||
@@ -251,10 +251,10 @@ let integralPeaks = 0; | ||
if (integralType === 'sum') { | ||
integral.value = xyIntegration(data, { | ||
from: integral.from, | ||
to: integral.to, | ||
integration.value = xyIntegration(data, { | ||
from: integration.from, | ||
to: integration.to, | ||
}); | ||
} else { | ||
integral.value = integralPeaks; | ||
integration.value = integralPeaks; | ||
} | ||
spectrumIntegral += integral.value; | ||
spectrumIntegral += integration.value; | ||
} | ||
@@ -265,4 +265,4 @@ | ||
for (let i = 0; i < signals.length; i++) { | ||
let integral = signals[i].integralData; | ||
integral.value *= integralFactor; | ||
let integration = signals[i].integralData; | ||
integration.value *= integralFactor; | ||
} | ||
@@ -269,0 +269,0 @@ } |
import fetch from 'cross-fetch'; | ||
import { addDiastereotopicMissingChirality } from 'openchemlib-utils'; | ||
import { signalsToRanges } from '../signals/signalsToRanges'; | ||
import { createInputJSON } from './utils/createInputJSON'; | ||
import { flatGroupedDiaIDs } from './utils/flatGroupedDiaIDs'; | ||
import { getFilteredIDiaIDs } from './utils/getFilteredIDiaIDs'; | ||
import { queryByHose } from './utils/queryByHOSE'; | ||
@@ -22,18 +22,34 @@ | ||
/** | ||
* Make a query to a hose code based database to predict carbon chemical shift | ||
* @param {Molecule} molecule - openchemlib molecule instance. | ||
* @param {object} options | ||
* @param {string} options.url - url of a custom database. | ||
* @param {object} options.database - custom database, each entry in the levels should has | ||
* an array as value [median] or [median, mean, sd, min, max, nb] for statistic purpose. | ||
* @param {number} options.maxSphereSize - max level to take into account in the query. If is not specified | ||
* the max level in the database will be used. | ||
* @returns {Promise<object>} - object with molfile, diaIDs, signals, joined signals by diaIDs and ranges. | ||
*/ | ||
export async function predictCarbon(molecule, options = {}) { | ||
let { levels = [3, 2, 1, 0], url, database } = options; | ||
let { url, database } = options; | ||
database = database || (await loadDB(url)); | ||
molecule.addImplicitHydrogens(); | ||
molecule.addMissingChirality(); | ||
addDiastereotopicMissingChirality(molecule); | ||
const maxLevel = database.length - 1; | ||
const molfile = molecule.toMolfile(); | ||
let { maxSphereSize = maxLevel } = options; | ||
const inputJSON = createInputJSON(molecule, { | ||
levels, | ||
}); | ||
if (maxSphereSize > maxLevel) maxSphereSize = maxLevel; | ||
let predictions = queryByHose(inputJSON, database, { | ||
levels, | ||
const { groupedDiaIDs, carbonDiaIDs, molfile } = getFilteredIDiaIDs( | ||
molecule, | ||
{ | ||
maxSphereSize, | ||
}, | ||
); | ||
let predictions = queryByHose(carbonDiaIDs, database, { | ||
maxSphereSize, | ||
}); | ||
@@ -45,6 +61,7 @@ | ||
molfile, | ||
diaIDs: inputJSON.diaIDs.map((e) => e.diaId), | ||
diaIDs: flatGroupedDiaIDs(groupedDiaIDs), | ||
joinedSignals, | ||
signals, | ||
ranges: signalsToRanges(joinedSignals), | ||
molecule, | ||
}; | ||
@@ -56,9 +73,10 @@ } | ||
for (const prediction of predictions) { | ||
const { atomIDs, nbAtoms, delta, diaIDs } = prediction; | ||
const { atomIDs, nbAtoms, delta, diaIDs, statistic } = prediction; | ||
signals.push({ | ||
delta, | ||
assignment: atomIDs, | ||
diaID: diaIDs, | ||
diaIDs: diaIDs, | ||
nbAtoms, | ||
j: [], | ||
statistic, | ||
js: [], | ||
}); | ||
@@ -72,3 +90,3 @@ } | ||
for (let signal of signals) { | ||
let diaID = signal.diaID[0]; | ||
let diaID = signal.diaIDs[0]; | ||
if (!joinedSignals[diaID]) { | ||
@@ -75,0 +93,0 @@ joinedSignals[diaID] = JSON.parse(JSON.stringify(signal)); |
@@ -17,3 +17,3 @@ import fetch from 'cross-fetch'; | ||
* @param {function} [options.cache] A callback receiving a molfile and the result | ||
* @return {Promise<Array>} | ||
* @returns {Promise<object>} - object with molfile, diaIDs, signals, joined signals by diaIDs and ranges. | ||
*/ | ||
@@ -53,2 +53,3 @@ export async function predictProton(molecule, options = {}) { | ||
ranges: signalsToRanges(joinedSignals), | ||
molecule, | ||
}; | ||
@@ -67,17 +68,17 @@ } | ||
assignment: [atom], | ||
diaID: [diaIDs[atom]], | ||
diaIDs: [diaIDs[atom]], | ||
nbAtoms: 1, | ||
delta: Number(fields[2]), | ||
j: [], | ||
js: [], | ||
}; | ||
for (let i = 0; i < couplings.length; i += 3) { | ||
let linked = Number(couplings[i] - 1); | ||
signal.j.push({ | ||
signal.js.push({ | ||
coupling: Number(couplings[i + 2]), | ||
assignment: [linked], | ||
diaID: [diaIDs[linked]], | ||
diaIDs: [diaIDs[linked]], | ||
multiplicity: 'd', | ||
distance: distanceMatrix[atom][linked], | ||
}); | ||
signal.j.sort((a, b) => b.coupling - a.coupling); | ||
signal.js.sort((a, b) => b.coupling - a.coupling); | ||
} | ||
@@ -84,0 +85,0 @@ signals.push(signal); |
@@ -1,18 +0,18 @@ | ||
export function queryByHose(input, db, options) { | ||
const { levels } = options; | ||
export function queryByHose(diaIDs, db, options) { | ||
const { maxSphereSize } = options; | ||
levels.sort((a, b) => b - a); | ||
const toReturn = []; | ||
for (const element of input.diaIDs) { | ||
for (const element of diaIDs) { | ||
let res; | ||
let k = 0; | ||
while (!res && k < levels.length) { | ||
if (db[levels[k]]) { | ||
res = db[levels[k]][element.hose[levels[k]]]; | ||
let level; | ||
for (let k = maxSphereSize; !res && k >= 0; k--) { | ||
if (db[k]) { | ||
res = db[k][element.hose[k]]; | ||
level = k; | ||
} | ||
k++; | ||
} | ||
if (!res) { | ||
res = [null]; | ||
k = 0; | ||
level = null; | ||
} | ||
@@ -25,3 +25,12 @@ | ||
atom.nbAtoms = 1; | ||
atom.level = level; | ||
if (res.length > 1) { | ||
atom.statistic = { | ||
mean: res[1], | ||
sd: res[2], | ||
min: res[3], | ||
max: res[4], | ||
nb: res[5], | ||
}; | ||
} | ||
toReturn.push(atom); | ||
@@ -28,0 +37,0 @@ } |
@@ -98,4 +98,4 @@ import { signalJoinCouplings } from '../signal/signalJoinCouplings'; | ||
if (Array.isArray(range.signal)) { | ||
range.signal = range.signal.filter( | ||
if (Array.isArray(range.signals)) { | ||
range.signals = range.signals.filter( | ||
(signal) => !uselessKind(signal.kind, options.filter), | ||
@@ -105,4 +105,4 @@ ); | ||
if (Array.isArray(range.signal) && range.signal.length > 0) { | ||
let signals = range.signal; | ||
if (Array.isArray(range.signals) && range.signals.length > 0) { | ||
let signals = range.signals; | ||
if (signals.length > 1) { | ||
@@ -153,15 +153,15 @@ if (options.ascending === true) { | ||
function getIntegral(range, options) { | ||
let integral = ''; | ||
let integration = ''; | ||
if (range.pubIntegral) { | ||
integral = range.pubIntegral; | ||
} else if (range.integral) { | ||
integral = | ||
range.integral.toFixed(0) + options.nucleus[options.nucleus.length - 1]; | ||
integration = range.pubIntegral; | ||
} else if (range.integration) { | ||
integration = | ||
range.integration.toFixed(0) + options.nucleus[options.nucleus.length - 1]; | ||
} | ||
return integral; | ||
return integration; | ||
} | ||
function pushIntegral(range, parenthesis, options) { | ||
let integral = getIntegral(range, options); | ||
if (integral.length > 0) parenthesis.push(integral); | ||
let integration = getIntegral(range, options); | ||
if (integration.length > 0) parenthesis.push(integration); | ||
} | ||
@@ -221,4 +221,4 @@ | ||
function pushCoupling(signal, parenthesis, options) { | ||
if (Array.isArray(signal.j) && signal.j.length > 0) { | ||
signal.j.sort(function (a, b) { | ||
if (Array.isArray(signal.js) && signal.js.length > 0) { | ||
signal.js.sort(function (a, b) { | ||
return b.coupling - a.coupling; | ||
@@ -228,3 +228,3 @@ }); | ||
let values = []; | ||
for (let j of signal.j) { | ||
for (let j of signal.js) { | ||
if (j.coupling !== undefined) { | ||
@@ -231,0 +231,0 @@ values.push(j.coupling.toFixed(options.nbDecimalJ)); |
@@ -21,9 +21,9 @@ import sum from 'ml-array-sum'; | ||
if (!signal.j || signal.j.length < 2) return signal; | ||
if (!signal.js || signal.js.length < 2) return signal; | ||
// we group the couplings that are less than the expected tolerance | ||
let currentGroup = [signal.j[0]]; | ||
let currentGroup = [signal.js[0]]; | ||
let groups = [currentGroup]; | ||
for (let i = 1; i < signal.j.length; i++) { | ||
let currentJ = signal.j[i]; | ||
for (let i = 1; i < signal.js.length; i++) { | ||
let currentJ = signal.js[i]; | ||
if ( | ||
@@ -40,3 +40,3 @@ currentGroup[currentGroup.length - 1].coupling - currentJ.coupling < | ||
signal.j = []; | ||
signal.js = []; | ||
for (let group of groups) { | ||
@@ -50,6 +50,6 @@ let coupling = sum(group.map((group) => group.coupling)) / group.length; | ||
); | ||
let diaID = distinctValues( | ||
let diaIDs = distinctValues( | ||
group | ||
.filter((group) => group.diaID && group.diaID.length > 0) | ||
.map((group) => group.diaID) | ||
.filter((group) => group.diaIDs && group.diaIDs.length > 0) | ||
.map((group) => group.diaIDs) | ||
.flat(), | ||
@@ -64,6 +64,6 @@ ); | ||
}; | ||
if (diaID.length > 0) newJ.diaID = diaID; | ||
if (diaIDs.length > 0) newJ.diaIDs = diaIDs; | ||
if (distances.length === 1 && distances[0]) newJ.distance = distances[0]; | ||
if (assignment.length > 0) newJ.assignment = assignment; | ||
signal.j.push(newJ); | ||
signal.js.push(newJ); | ||
} | ||
@@ -70,0 +70,0 @@ |
@@ -7,3 +7,3 @@ /** | ||
export function signalMultiplicityPattern(signal) { | ||
let js = signal.j; | ||
let js = signal.js; | ||
let pattern = ''; | ||
@@ -10,0 +10,0 @@ if (js && js.length > 0) { |
/** | ||
* Ensure that assignment and diaID are arrays and coupling are sorted | ||
* Ensure that assignment and diaIDs are arrays and coupling are sorted | ||
* @param {object} signal | ||
@@ -12,7 +12,7 @@ * @returns signal | ||
} | ||
if (signal.diaID && !Array.isArray(signal.diaID)) { | ||
signal.diaID = [signal.diaID]; | ||
if (signal.diaIDs && !Array.isArray(signal.diaIDs)) { | ||
signal.diaIDs = [signal.diaIDs]; | ||
} | ||
if (signal.j) { | ||
let couplings = signal.j; | ||
if (signal.js) { | ||
let couplings = signal.js; | ||
for (let coupling of couplings) { | ||
@@ -22,9 +22,9 @@ if (coupling.assignment && !Array.isArray(coupling.assignment)) { | ||
} | ||
if (coupling.diaID && !Array.isArray(coupling.diaID)) { | ||
coupling.diaID = [coupling.diaID]; | ||
if (coupling.diaIDs && !Array.isArray(coupling.diaIDs)) { | ||
coupling.diaIDs = [coupling.diaIDs]; | ||
} | ||
} | ||
signal.j = signal.j.sort((a, b) => b.coupling - a.coupling); | ||
signal.js = signal.js.sort((a, b) => b.coupling - a.coupling); | ||
} | ||
return signal; | ||
} |
@@ -14,7 +14,7 @@ import mean from 'ml-array-mean'; | ||
for (let signal of signals) { | ||
if (!signal.diaID || !signal.diaID.length === 1) return signals; | ||
for (let coupling of signal.j) { | ||
if (!signal.diaIDs || !signal.diaIDs.length === 1) return signals; | ||
for (let coupling of signal.js) { | ||
if ( | ||
!coupling.diaID || | ||
!coupling.diaID.length === 1 || | ||
!coupling.diaIDs || | ||
!coupling.diaIDs.length === 1 || | ||
coupling.multiplicity !== 'd' | ||
@@ -31,7 +31,7 @@ ) { | ||
signal = signalNormalize(signal); // we have a copy | ||
signal.j = signal.j.sort((a, b) => | ||
a.diaID + a.distance < b.diaID + b.distance ? 1 : -1, | ||
signal.js = signal.js.sort((a, b) => | ||
a.diaIDs + a.distance < b.diaIDs + b.distance ? 1 : -1, | ||
); | ||
let id = `${signal.diaID[0]} ${signal.j | ||
.map((j) => `${j.diaID[0]} ${j.distance}`) | ||
let id = `${signal.diaIDs[0]} ${signal.js | ||
.map((j) => `${j.diaIDs[0]} ${j.distance}`) | ||
.sort() | ||
@@ -51,9 +51,9 @@ .join(' ')}`; | ||
// joining couplings only if diaID and distance are equal | ||
const j = []; | ||
for (let i = 0; i < group[0].j.length; i++) { | ||
j.push({ | ||
diaID: group[0].j[i].diaID, | ||
distance: group[0].j[i].distance, | ||
multiplicity: group[0].j[i].multiplicity, | ||
coupling: mean(group.map((item) => item.j[i].coupling)), | ||
const js = []; | ||
for (let i = 0; i < group[0].js.length; i++) { | ||
js.push({ | ||
diaIDs: group[0].js[i].diaIDs, | ||
distance: group[0].js[i].distance, | ||
multiplicity: group[0].js[i].multiplicity, | ||
coupling: mean(group.map((item) => item.js[i].coupling)), | ||
}); | ||
@@ -65,3 +65,3 @@ } | ||
delta: mean(group.map((item) => item.delta)), | ||
diaID: group[0].diaID, | ||
diaIDs: group[0].diaIDs, | ||
assignment: group | ||
@@ -71,3 +71,3 @@ .map((item) => item.assignment) | ||
.filter((item) => item), | ||
j, | ||
js, | ||
}); | ||
@@ -78,4 +78,4 @@ } | ||
signal = signalNormalize(signalJoinCouplings(signal, { tolerance })); | ||
if (signal.j) { | ||
signal.multiplicity = signal.j.reduce((multiplicity, jCoupling) => { | ||
if (signal.js) { | ||
signal.multiplicity = signal.js.reduce((multiplicity, jCoupling) => { | ||
return `${multiplicity}${jCoupling.multiplicity}`; | ||
@@ -82,0 +82,0 @@ }, ''); |
@@ -8,3 +8,3 @@ export function signalsToRanges(signals, options = {}) { | ||
let halfWidth = | ||
(signal.original.j || []).reduce( | ||
(signal.original.js || []).reduce( | ||
(total, j) => (total += j.coupling / frequency), | ||
@@ -26,10 +26,10 @@ 0, | ||
to: signal.to, | ||
integral: signal.original.nbAtoms, | ||
signal: [signal.original], | ||
integration: signal.original.nbAtoms, | ||
signals: [signal.original], | ||
}; | ||
ranges.push(range); | ||
} else { | ||
range.integral += signal.original.nbAtoms; | ||
range.integration += signal.original.nbAtoms; | ||
if (signal.to > range.to) range.to = signal.to; | ||
range.signal.push(signal.original); | ||
range.signals.push(signal.original); | ||
} | ||
@@ -36,0 +36,0 @@ } |
@@ -18,3 +18,3 @@ import Matrix from 'ml-matrix'; | ||
for (let i = 0; i < nSpins; i++) { | ||
let { assignment: signalAssignment, j: jCoupling } = signals[i]; | ||
let { assignment: signalAssignment, js: jCoupling } = signals[i]; | ||
for (let k = 0; k < jCoupling.length; k++) { | ||
@@ -21,0 +21,0 @@ let { coupling, assignment } = jCoupling[k]; |
@@ -25,3 +25,3 @@ import { peaksFilterImpurities } from '../peaks/peaksFilterImpurities'; | ||
* @param {Boolean} [options.peakPicking.optimize=true] - if it's true adjust an train of gaussian or lorentzian shapes to spectrum. | ||
* @param {Number} [options.ranges.integrationSum=100] - Number of hydrogens or some number to normalize the integral data. If it's zero return the absolute integral value | ||
* @param {Number} [options.ranges.integrationSum=100] - Number of hydrogens or some number to normalize the integration data. If it's zero return the absolute integration value | ||
* @param {String} [options.ranges.integralType='sum'] - option to chose between approx area with peaks or the sum of the points of given range ('sum', 'peaks') | ||
@@ -37,3 +37,3 @@ * @param {Number} [options.ranges.frequencyCluster=16] - distance limit to clustering peaks. | ||
* @param {string} [options.impurities.error=0.025] - tolerance in ppm to assign a impurity. | ||
* @returns {array} - Array of ranges with {from, to, integral, signals: [{delta, j, multiplicity, peaks}]} | ||
* @returns {array} - Array of ranges with {from, to, integration, signals: [{delta, j, multiplicity, peaks}]} | ||
*/ | ||
@@ -40,0 +40,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
370286
45
13368
Updatedml-spectra-processing@^6.8.0