Skip to content

Commit

Permalink
feat: export to nmredata (#969)
Browse files Browse the repository at this point in the history
* feat: export to nmredata

* feat: add 1d signals does not assigned to nmredata
  • Loading branch information
jobo322 committed Apr 7, 2021
1 parent f4add13 commit c06e5ac
Show file tree
Hide file tree
Showing 11 changed files with 23,468 additions and 17 deletions.
23,112 changes: 23,103 additions & 9 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -57,6 +57,7 @@
"file-saver": "^2.0.5",
"formik": "^2.2.6",
"immer": "^9.0.1",
"jszip": "^3.6.0",
"lodash": "^4.17.21",
"mf-parser": "^1.1.10",
"ml-airpls": "^1.0.0",
Expand All @@ -75,6 +76,7 @@
"nmr-processing": "^1.2.0",
"numeral": "^2.0.6",
"openchemlib": "^7.4.0",
"openchemlib-utils": "^1.0.0",
"prismjs": "^1.23.0",
"prop-types": "^15.7.2",
"re-resizable": "^6.9.0",
Expand Down
3 changes: 2 additions & 1 deletion src/component/reducer/actions/ExportActions.ts
Expand Up @@ -8,6 +8,7 @@ import {
exportAsPng,
copyPNGToClipboard,
} from '../../utility/Export';
import { toNmredata } from '../../utility/toNmredata';
import { State } from '../Reducer';

function exportData(draft: Draft<State>, { exportType, spaceIndent }) {
Expand All @@ -24,7 +25,7 @@ function exportData(draft: Draft<State>, { exportType, spaceIndent }) {
break;
}
case 'nmre': {
const exportedData = toJSON(state);
const exportedData = toNmredata(state);
exportAsNMRE(exportedData, fileName);
break;
}
Expand Down
10 changes: 3 additions & 7 deletions src/component/utility/Export.js
Expand Up @@ -71,13 +71,9 @@ function exportAsJSON(data, fileName = 'experiment', spaceIndent) {
saveAs(blob, `${fileName}.nmrium`);
}
function exportAsNMRE(data, fileName = 'experiment') {
const fileData = JSON.stringify(
data,
(key, value) => (ArrayBuffer.isView(value) ? Array.from(value) : value),
0,
);
const blob = new Blob([fileData], { type: 'text/plain' });
saveAs(blob, `${fileName}.nmrium`);
data.generateAsync({ type: 'blob' }).then((content) => {
saveAs(content, `${fileName}.zip`);
});
}

function exportAsMol(data, fileName = 'mol') {
Expand Down
83 changes: 83 additions & 0 deletions src/component/utility/toNmredata.js
@@ -0,0 +1,83 @@
import jszip from 'jszip';
import { getGroupedDiastereotopicAtomIDs } from 'openchemlib-utils';
import { Molecule as OCLMolecule } from 'openchemlib/full';

import { get1DSignals } from './util/get1DSignals';
import { get2DSignals } from './util/get2DSignals';
import { getLabels } from './util/getLabels';

const tags = {
solvent: 'SOLVENT',
temperature: 'TEMPERATURE',
assignment: 'ASSIGNMENT',
j: 'J',
signals: 'SIGNALS',
id: 'ID',
};

export function toNmredata(state, options = {}) {
const {
data, // it would be changed depending of the final location
molecules,
} = state || {
data: [], // it would be changed depending of the final location
molecules: [],
};

const { id, prefix = '\n> <NMREDATA_', filename = 'nmredata' } = options;

let sdfResult = '';
let nmrRecord = new jszip();

let molecule = OCLMolecule.fromMolfile(molecules[0].molfile);
molecule.addImplicitHydrogens();
let groupedDiaIDs = getGroupedDiastereotopicAtomIDs(molecule);

let groupedOptions = {
prefix,
molecule,
groupedDiaIDs,
nmrRecord,
};

sdfResult += molecules[0].molfile;
let labels = getLabels(data, groupedOptions);
sdfResult += `${prefix}VERSION>\n1.1\\\n`;
sdfResult += putTag(data, 'temperature', { prefix });
sdfResult += putTag(data, 'solvent', { prefix });

if (id) {
sdfResult += `${prefix + tags.id}>\nid\\\n`;
}

sdfResult += formatAssignments(labels.byDiaID, groupedOptions);
sdfResult += get1DSignals(data, labels, groupedOptions);
sdfResult += get2DSignals(data, labels, groupedOptions);
sdfResult += '\n$$$$\n';
nmrRecord.file(`${filename}.sdf`, sdfResult);
return nmrRecord;
}

function formatAssignments(labels, options) {
const { prefix } = options;
let str = `${prefix + tags.assignment}>\n`;
for (let l in labels) {
let atoms = labels[l].atoms;
str += `${labels[l].label}, ${labels[l].shift}`; // change to add label
for (let atom of atoms) str += `, ${atom}`;
str += '\\\n';
}
return str;
}

function putTag(spectra, tag, options = {}) {
const { prefix } = options;
let str = '';
for (let spectrum of spectra) {
if (spectrum.info[tag]) {
str += `${prefix + tags[tag]}>\n${String(spectrum.info[tag])}\\\n`;
break;
}
}
return str;
}
8 changes: 8 additions & 0 deletions src/component/utility/util/flat2DSignals.js
@@ -0,0 +1,8 @@
export function flat2DSignals(signals) {
let flattedSignal = [];
for (let i = 0; i < signals.length; i++) {
let { x, y } = signals[i];
flattedSignal.push(x, y);
}
return flattedSignal;
}
98 changes: 98 additions & 0 deletions src/component/utility/util/get1DSignals.js
@@ -0,0 +1,98 @@
import { getToFix } from './getToFix';

export function get1DSignals(data, labels, options = {}) {
const { prefix, nmrRecord } = options;
let str = '';
let nucleusArray = [];
for (let spectrum of data) {
if (spectrum.info.dimension > 1) continue;

let ranges = spectrum.ranges.values || [];

let nucleus = spectrum.info.nucleus;
let counter = 1;
let subfix = '';
nucleusArray.forEach((e) => {
if (e === nucleus) counter++;
});
nucleusArray.push(nucleus);

if (counter > 1) subfix = `#${counter}`;

str += `${prefix}1D_${nucleus.toUpperCase()}#${subfix}>`;

if (spectrum.info.frequency) {
str += `\nLarmor=${Number(spectrum.info.frequency).toFixed(2)}\\`;
}

if (spectrum.source.jcamp) {
nmrRecord.file(
`jcamp_folder/1d/${spectrum.display.name}`,
spectrum.source.jcamp,
);
str += `\nSpectrum_Jcamp=file:./jcamp_folder/1d/${spectrum.display.name}\\`;
}

let toFix = getToFix(nucleus)[0];

for (let range of ranges) {
let signals = range.signal; //.filter((s) => s.diaID && s.diaID.length);

for (let signal of signals) {
let { multiplicity } = signal;
if ((!multiplicity || multiplicity === 'm') && nucleus === '1H') {
str += `\n${Number(range.from).toFixed(toFix)}-${Number(
range.to,
).toFixed(toFix)}`;
} else if (signal.delta) {
str += `\n${Number(signal.delta).toFixed(toFix)}`;
} else {
continue;
}

let signalLabel = '';

if (signal.diaID && signal.diaID.length > 0) {
signal.diaID.forEach((diaID, i, arr) => {
let separator = ', ';
if (i === arr.length - 1) separator = '';
let label = labels.byDiaID[diaID].label || diaID;
signalLabel += `(${label})${separator}`;
});
str += `, L=${signalLabel}`;
}

if (nucleus === '1H') {
if (signal.multiplicity) str += `, S=${signal.multiplicity}`;

let jCoupling = signal.j;
if (Array.isArray(jCoupling) && jCoupling.length) {
let separator = ', J=';
for (let i = 0; i < jCoupling.length; i++) {
str += `${separator}${Number(jCoupling[i].coupling).toFixed(3)}`;
if (jCoupling[i].diaID) {
let { diaID } = jCoupling[i];
if (!Array.isArray(diaID)) diaID = [diaID];
if (!diaID.length) continue;
let jCouple = labels[diaID[0]].label || String(diaID[0]);
str += `(${jCouple})`;
}
separator = ', ';
}
}
if (range.integral) {
str += `, E=${Number(range.integral).toFixed(toFix)}`;
} else if (range.pubIntegral) {
str += `, E=${range.putIntegral.toFixed(toFix)}`;
} else if (range.signal[0].nbAtoms !== undefined) {
str += `, E=${range.signal[0].nbAtoms}`;
}
}
}
if (signals.length) str += '\\';
}
str += '\n';
}

return str;
}
64 changes: 64 additions & 0 deletions src/component/utility/util/get2DSignals.js
@@ -0,0 +1,64 @@
import { getCouplingObserved } from './getCouplingObserved';
import { getToFix } from './getToFix';

const isArray = Array.isArray;

export function get2DSignals(data, labels, options = {}) {
let { prefix, nmrRecord } = options;
let { byDiaID } = labels;
let str = '';
let nucleusRecorded = [];
for (let spectrum of data) {
if (spectrum.info.dimension < 2) continue;

let { nucleus, experiment, pulseSequence } = spectrum.info;

if (experiment) prefix = `\n> 2D ${experiment} <NMREDATA_2D_`;

nucleusRecorded.push(nucleus);

let couplingObserved = getCouplingObserved(experiment);
if (nucleus) {
str += `${prefix}${nucleus[1]}_${couplingObserved}_${nucleus[0]}>`;
}
let toFix = getToFix(nucleus);

str += `\nLarmor=${Number(spectrum.info.baseFrequency[0]).toFixed(2)}\\`;

if (spectrum.source.jcamp) {
let pathPrefix = 'jcamp_folder/1d/';
nmrRecord.file(
`${pathPrefix}'${spectrum.display.name}`,
spectrum.source.jcamp,
);
str += `\nSpectrum_Jcamp=file:./${pathPrefix}${spectrum.display.name}\\`;
}

if (experiment) str += `\nCorType=${experiment} \\`;
if (pulseSequence) str += `\nPulseProgram=${pulseSequence} \\`;

let zones = spectrum.zones.values || [];
for (let zone of zones) {
let signals = zone.signal;
for (let signal of signals) {
let { x, y, peak } = signal;
let xLabel = getAssignment(x, byDiaID, toFix[0]);
let yLabel = getAssignment(y, byDiaID, toFix[1]);
let intensity = Math.max(...peak.map((e) => e.z));
str += `\n${xLabel}/${yLabel}, I=${intensity.toFixed(2)}\\`;
}
}
}
return str;
}

function getAssignment(axis, labels, toFix) {
let { diaID, delta } = axis;
if (diaID) {
if (!isArray(diaID)) diaID = [diaID];
if (diaID.length < 1) Number(delta).toFixed(toFix);
let label = diaID.map((diaID) => labels[diaID].label).join(',');
return diaID.length > 1 ? `(${label})` : label;
}
return Number(delta).toFixed(toFix);
}
11 changes: 11 additions & 0 deletions src/component/utility/util/getCouplingObserved.js
@@ -0,0 +1,11 @@
export function getCouplingObserved(experiment) {
switch (experiment.toLowerCase()) {
case 'hsqc':
case 'cosy':
return '1J';
case 'hmbc':
return 'NJ';
default:
return 'NJ';
}
}
75 changes: 75 additions & 0 deletions src/component/utility/util/getLabels.js
@@ -0,0 +1,75 @@
import { getShortestPaths } from 'openchemlib-utils';

import { flat2DSignals } from './flat2DSignals';
import { getToFix } from './getToFix';

export function getLabels(data, options = {}) {
const { groupedDiaIDs, molecule } = options;

let connections = getShortestPaths(molecule, { toLabel: 'H', maxLength: 1 });

let byDiaID = {};
let byAssignNumber = {};
for (let spectrum of data) {
let { dimension, nucleus } = spectrum.info;
let toFix = getToFix(nucleus);

let [roiKey, flatSignals] =
dimension > 1 ? ['zones', flat2DSignals] : ['ranges', (s) => s || []];

let rois = spectrum[roiKey].values || [];
for (let roi of rois) {
let signals = flatSignals(roi.signal);
for (let i = 0; i < signals.length; i++) {
let diaIDs = signals[i].diaID || [];
for (let diaID of diaIDs) {
let delta = Number(signals[i].delta).toFixed(toFix[i % dimension]);
// get atomLabel
let groupedOclID = groupedDiaIDs.find((dia) => {
if (dia.oclID === diaID) return true;
return false;
});
// the addition of one in atom number it is because the atoms enumeration starts from zero

let labelOptions = {
atom: groupedOclID.atoms[0],
molecule,
connections,
atomLabel: groupedOclID.atomLabel,
};

byDiaID[diaID] = {
atoms: groupedOclID.atoms.map((e) => e + 1),
shift: delta,
label: createLabel(labelOptions),
};

for (let atom of groupedOclID.atoms) {
labelOptions.atom = atom;
byAssignNumber[atom] = {
shift: delta,
diaID,
label: createLabel(labelOptions),
};
}
}
}
}
}
return { byAssignNumber, byDiaID };
}

function createLabel(options) {
const { atom, molecule, atomLabel, connections } = options;
let label = '';
if (atomLabel !== 'C') {
let connectedTo = connections[atom];
let path = connectedTo.find((e) => e && e.length > 1);
let pLabel = `${atomLabel}${path[0] + 1}`;
let hLabel = `${molecule.getAtomLabel(path[1])}${path[1] + 1}`;
label = `${pLabel}${hLabel}`;
} else {
label = `${atomLabel}${atom + 1}`;
}
return label;
}

0 comments on commit c06e5ac

Please sign in to comment.