jcampconverter
Advanced tools
Comparing version 4.1.1 to 5.0.0
@@ -0,1 +1,10 @@ | ||
# [5.0.0](https://github.com/cheminfo-js/jcampconverter/compare/v4.1.1...v5.0.0) (2020-04-04) | ||
### Bug Fixes | ||
* rollup and build ([eef5d66](https://github.com/cheminfo-js/jcampconverter/commit/eef5d66dbeccdfd907dc86ce1cd8aa1429a544f3)) | ||
## [4.1.1](https://github.com/cheminfo-js/jcampconverter/compare/v4.1.0...v4.1.1) (2020-04-02) | ||
@@ -2,0 +11,0 @@ |
{ | ||
"name": "jcampconverter", | ||
"version": "4.1.1", | ||
"version": "5.0.0", | ||
"description": "Parse and convert JCAMP data", | ||
"main": "./src/index.js", | ||
"main": "lib/index.js", | ||
"module": "src/index.js", | ||
"files": [ | ||
"lib", | ||
"src" | ||
@@ -17,2 +19,5 @@ ], | ||
"scripts": { | ||
"build": "cheminfo-build --entry src/index.js --root JcampConverter", | ||
"compile": "rollup -c", | ||
"prepublishOnly": "npm run compile", | ||
"eslint": "eslint src __tests__", | ||
@@ -23,3 +28,2 @@ "eslint-fix": "npm run eslint -- --fix", | ||
"test-only": "jest", | ||
"build": "cheminfo build --root JcampConverter --no-source-map", | ||
"benchmark": "node benchmark/benchmark.js", | ||
@@ -47,3 +51,6 @@ "deopt": "node --trace-opt --trace-deopt --code-comments debug/deoptimize.js > deopt.log", | ||
"devDependencies": { | ||
"@babel/plugin-transform-modules-commonjs": "^7.9.0", | ||
"@types/jest": "^25.1.5", | ||
"benchmark": "^2.1.4", | ||
"cheminfo-build": "^1.1.0", | ||
"cheminfo-tools": "^1.23.3", | ||
@@ -55,5 +62,9 @@ "eslint": "^6.8.0", | ||
"eslint-plugin-prettier": "^3.1.2", | ||
"jest": "^25.2.4", | ||
"prettier": "^2.0.2" | ||
"jest": "^25.2.6", | ||
"prettier": "^2.0.2", | ||
"rollup": "^2.3.2" | ||
}, | ||
"dependencies": { | ||
"ml-array-median": "^1.1.3" | ||
} | ||
} |
@@ -61,2 +61,8 @@ # JCAMP converter | ||
var result = converter.convert(jcamp); | ||
// if there is only one spectrum it will be in | ||
console.log(result.flatten[0]); | ||
// the converter will also keep the full jcamp tree | ||
``` | ||
@@ -63,0 +69,0 @@ |
1088
src/index.js
@@ -1,1086 +0,2 @@ | ||
'use strict'; | ||
function getConverter() { | ||
// the following RegExp can only be used for XYdata, some peakTables have values with a "E-5" ... | ||
const ntuplesSeparator = /[, \t]+/; | ||
const GC_MS_FIELDS = ['TIC', '.RIC', 'SCANNUMBER']; | ||
function convertToFloatArray(stringArray) { | ||
let floatArray = []; | ||
for (let i = 0; i < stringArray.length; i++) { | ||
floatArray.push(parseFloat(stringArray[i])); | ||
} | ||
return floatArray; | ||
} | ||
class Spectrum {} | ||
const defaultOptions = { | ||
keepRecordsRegExp: /^$/, | ||
canonicDataLabels: true, | ||
dynamicTyping: false, | ||
xy: false, | ||
withoutXY: false, | ||
chromatogram: false, | ||
keepSpectra: false, | ||
noContour: false, | ||
nbContourLevels: 7, | ||
noiseMultiplier: 5, | ||
profiling: false, | ||
}; | ||
function convert(jcamp, options) { | ||
options = Object.assign({}, defaultOptions, options); | ||
let wantXY = !options.withoutXY; | ||
let start = Date.now(); | ||
let ntuples = {}; | ||
let ldr, dataValue, ldrs; | ||
let position, endLine, infos; | ||
let result = {}; | ||
result.profiling = options.profiling ? [] : false; | ||
result.logs = []; | ||
let spectra = []; | ||
result.spectra = spectra; | ||
result.info = {}; | ||
let spectrum = new Spectrum(); | ||
if (!(typeof jcamp === 'string')) { | ||
throw new TypeError('the JCAMP should be a string'); | ||
} | ||
if (result.profiling) { | ||
result.profiling.push({ | ||
action: 'Before split to LDRS', | ||
time: Date.now() - start, | ||
}); | ||
} | ||
ldrs = jcamp.split(/[\r\n]+##/); | ||
if (result.profiling) { | ||
result.profiling.push({ | ||
action: 'Split to LDRS', | ||
time: Date.now() - start, | ||
}); | ||
} | ||
if (ldrs[0]) ldrs[0] = ldrs[0].replace(/^[\r\n ]*##/, ''); | ||
for (let i = 0; i < ldrs.length; i++) { | ||
let dataLabel; | ||
ldr = ldrs[i]; | ||
// This is a new LDR | ||
position = ldr.indexOf('='); | ||
if (position > 0) { | ||
dataLabel = ldr.substring(0, position); | ||
dataValue = ldr.substring(position + 1).trim(); | ||
} else { | ||
dataLabel = ldr; | ||
dataValue = ''; | ||
} | ||
let canonicDataLabel = dataLabel.replace(/[_ -]/g, '').toUpperCase(); | ||
if (canonicDataLabel === 'DATATABLE') { | ||
endLine = dataValue.indexOf('\n'); | ||
if (endLine === -1) endLine = dataValue.indexOf('\r'); | ||
if (endLine > 0) { | ||
let xIndex = -1; | ||
let yIndex = -1; | ||
// ##DATA TABLE= (X++(I..I)), XYDATA | ||
// We need to find the variables | ||
infos = dataValue.substring(0, endLine).split(/[ ,;\t]+/); | ||
if (infos[0].indexOf('++') > 0) { | ||
let firstVariable = infos[0].replace( | ||
/.*\(([a-zA-Z0-9]+)\+\+.*/, | ||
'$1', | ||
); | ||
let secondVariable = infos[0].replace( | ||
/.*\.\.([a-zA-Z0-9]+).*/, | ||
'$1', | ||
); | ||
xIndex = ntuples.symbol.indexOf(firstVariable); | ||
yIndex = ntuples.symbol.indexOf(secondVariable); | ||
} | ||
if (xIndex === -1) xIndex = 0; | ||
if (yIndex === -1) yIndex = 0; | ||
if (ntuples.first) { | ||
if (ntuples.first.length > xIndex) { | ||
spectrum.firstX = ntuples.first[xIndex]; | ||
} | ||
if (ntuples.first.length > yIndex) { | ||
spectrum.firstY = ntuples.first[yIndex]; | ||
} | ||
} | ||
if (ntuples.last) { | ||
if (ntuples.last.length > xIndex) { | ||
spectrum.lastX = ntuples.last[xIndex]; | ||
} | ||
if (ntuples.last.length > yIndex) { | ||
spectrum.lastY = ntuples.last[yIndex]; | ||
} | ||
} | ||
if (ntuples.vardim && ntuples.vardim.length > xIndex) { | ||
spectrum.nbPoints = ntuples.vardim[xIndex]; | ||
} | ||
if (ntuples.factor) { | ||
if (ntuples.factor.length > xIndex) { | ||
spectrum.xFactor = ntuples.factor[xIndex]; | ||
} | ||
if (ntuples.factor.length > yIndex) { | ||
spectrum.yFactor = ntuples.factor[yIndex]; | ||
} | ||
} | ||
if (ntuples.units) { | ||
if (ntuples.units.length > xIndex) { | ||
spectrum.xUnit = ntuples.units[xIndex]; | ||
} | ||
if (ntuples.units.length > yIndex) { | ||
spectrum.yUnit = ntuples.units[yIndex]; | ||
} | ||
} | ||
spectrum.datatable = infos[0]; | ||
if (infos[1] && infos[1].indexOf('PEAKS') > -1) { | ||
canonicDataLabel = 'PEAKTABLE'; | ||
} else if ( | ||
infos[1] && | ||
(infos[1].indexOf('XYDATA') || infos[0].indexOf('++') > 0) | ||
) { | ||
canonicDataLabel = 'XYDATA'; | ||
spectrum.deltaX = | ||
(spectrum.lastX - spectrum.firstX) / (spectrum.nbPoints - 1); | ||
} | ||
} | ||
} | ||
if (canonicDataLabel === 'XYDATA') { | ||
if (wantXY) { | ||
prepareSpectrum(result, spectrum); | ||
// well apparently we should still consider it is a PEAK TABLE if there are no '++' after | ||
if (dataValue.match(/.*\+\+.*/)) { | ||
// ex: (X++(Y..Y)) | ||
if (!spectrum.deltaX) { | ||
spectrum.deltaX = | ||
(spectrum.lastX - spectrum.firstX) / (spectrum.nbPoints - 1); | ||
} | ||
fastParseXYData(spectrum, dataValue, result); | ||
} else { | ||
parsePeakTable(spectrum, dataValue, result); | ||
} | ||
spectra.push(spectrum); | ||
spectrum = new Spectrum(); | ||
} | ||
continue; | ||
} else if (canonicDataLabel === 'PEAKTABLE') { | ||
if (wantXY) { | ||
prepareSpectrum(result, spectrum); | ||
parsePeakTable(spectrum, dataValue, result); | ||
spectra.push(spectrum); | ||
spectrum = new Spectrum(); | ||
} | ||
continue; | ||
} | ||
if (canonicDataLabel === 'PEAKASSIGNMENTS') { | ||
if (wantXY) { | ||
if (dataValue.match(/.*(XYA).*/)) { | ||
// ex: (XYA) | ||
parseXYA(spectrum, dataValue); | ||
} | ||
spectra.push(spectrum); | ||
spectrum = new Spectrum(); | ||
} | ||
continue; | ||
} | ||
if (canonicDataLabel === 'TITLE') { | ||
spectrum.title = dataValue; | ||
} else if (canonicDataLabel === 'DATATYPE') { | ||
spectrum.dataType = dataValue; | ||
if (dataValue.indexOf('nD') > -1) { | ||
result.twoD = true; | ||
} | ||
} else if (canonicDataLabel === 'NTUPLES') { | ||
if (dataValue.indexOf('nD') > -1) { | ||
result.twoD = true; | ||
} | ||
} else if (canonicDataLabel === 'XUNITS') { | ||
spectrum.xUnit = dataValue; | ||
} else if (canonicDataLabel === 'YUNITS') { | ||
spectrum.yUnit = dataValue; | ||
} else if (canonicDataLabel === 'FIRSTX') { | ||
spectrum.firstX = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'LASTX') { | ||
spectrum.lastX = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'FIRSTY') { | ||
spectrum.firstY = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'LASTY') { | ||
spectrum.lastY = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'NPOINTS') { | ||
spectrum.nbPoints = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'XFACTOR') { | ||
spectrum.xFactor = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'YFACTOR') { | ||
spectrum.yFactor = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'MAXX') { | ||
spectrum.maxX = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'MINX') { | ||
spectrum.minX = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'MAXY') { | ||
spectrum.maxY = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'MINY') { | ||
spectrum.minY = parseFloat(dataValue); | ||
} else if (canonicDataLabel === 'DELTAX') { | ||
spectrum.deltaX = parseFloat(dataValue); | ||
} else if ( | ||
canonicDataLabel === '.OBSERVEFREQUENCY' || | ||
canonicDataLabel === '$SFO1' | ||
) { | ||
if (!spectrum.observeFrequency) { | ||
spectrum.observeFrequency = parseFloat(dataValue); | ||
} | ||
} else if (canonicDataLabel === '.OBSERVENUCLEUS') { | ||
if (!spectrum.xType) { | ||
result.xType = dataValue.replace(/[^a-zA-Z0-9]/g, ''); | ||
} | ||
} else if (canonicDataLabel === '$SFO2') { | ||
if (!result.indirectFrequency) { | ||
result.indirectFrequency = parseFloat(dataValue); | ||
} | ||
} else if (canonicDataLabel === '$OFFSET') { | ||
// OFFSET for Bruker spectra | ||
result.shiftOffsetNum = 0; | ||
if (!spectrum.shiftOffsetVal) { | ||
spectrum.shiftOffsetVal = parseFloat(dataValue); | ||
} | ||
} else if (canonicDataLabel === '$REFERENCEPOINT') { | ||
// OFFSET for Varian spectra | ||
// if we activate this part it does not work for ACD specmanager | ||
// } else if (canonicDataLabel=='.SHIFTREFERENCE') { // OFFSET FOR Bruker Spectra | ||
// var parts = dataValue.split(/ *, */); | ||
// result.shiftOffsetNum = parseInt(parts[2].trim()); | ||
// spectrum.shiftOffsetVal = parseFloat(parts[3].trim()); | ||
} else if (canonicDataLabel === 'VARNAME') { | ||
ntuples.varname = dataValue.split(ntuplesSeparator); | ||
} else if (canonicDataLabel === 'SYMBOL') { | ||
ntuples.symbol = dataValue.split(ntuplesSeparator); | ||
} else if (canonicDataLabel === 'VARTYPE') { | ||
ntuples.vartype = dataValue.split(ntuplesSeparator); | ||
} else if (canonicDataLabel === 'VARFORM') { | ||
ntuples.varform = dataValue.split(ntuplesSeparator); | ||
} else if (canonicDataLabel === 'VARDIM') { | ||
ntuples.vardim = convertToFloatArray(dataValue.split(ntuplesSeparator)); | ||
} else if (canonicDataLabel === 'UNITS') { | ||
ntuples.units = dataValue.split(ntuplesSeparator); | ||
} else if (canonicDataLabel === 'FACTOR') { | ||
ntuples.factor = convertToFloatArray(dataValue.split(ntuplesSeparator)); | ||
} else if (canonicDataLabel === 'FIRST') { | ||
ntuples.first = convertToFloatArray(dataValue.split(ntuplesSeparator)); | ||
} else if (canonicDataLabel === 'LAST') { | ||
ntuples.last = convertToFloatArray(dataValue.split(ntuplesSeparator)); | ||
} else if (canonicDataLabel === 'MIN') { | ||
ntuples.min = convertToFloatArray(dataValue.split(ntuplesSeparator)); | ||
} else if (canonicDataLabel === 'MAX') { | ||
ntuples.max = convertToFloatArray(dataValue.split(ntuplesSeparator)); | ||
} else if (canonicDataLabel === '.NUCLEUS') { | ||
if (result.twoD) { | ||
result.yType = dataValue.split(ntuplesSeparator)[0]; | ||
} | ||
} else if (canonicDataLabel === 'PAGE') { | ||
spectrum.page = dataValue.trim(); | ||
spectrum.pageValue = parseFloat(dataValue.replace(/^.*=/, '')); | ||
spectrum.pageSymbol = spectrum.page.replace(/[=].*/, ''); | ||
let pageSymbolIndex = ntuples.symbol.indexOf(spectrum.pageSymbol); | ||
let unit = ''; | ||
if (ntuples.units && ntuples.units[pageSymbolIndex]) { | ||
unit = ntuples.units[pageSymbolIndex]; | ||
} | ||
if (result.indirectFrequency && unit !== 'PPM') { | ||
spectrum.pageValue /= result.indirectFrequency; | ||
} | ||
} else if (canonicDataLabel === 'RETENTIONTIME') { | ||
spectrum.pageValue = parseFloat(dataValue); | ||
} else if (isMSField(canonicDataLabel)) { | ||
spectrum[convertMSFieldToLabel(canonicDataLabel)] = dataValue; | ||
} else if (canonicDataLabel === 'SAMPLEDESCRIPTION') { | ||
spectrum.sampleDescription = dataValue; | ||
} | ||
if (canonicDataLabel.match(options.keepRecordsRegExp)) { | ||
let label = options.canonicDataLabels ? canonicDataLabel : dataLabel; | ||
let value = dataValue.trim(); | ||
if (options.dynamicTyping && !isNaN(value)) { | ||
value = Number(value); | ||
} | ||
if (result.info[label]) { | ||
if (!Array.isArray(result.info[label])) { | ||
result.info[label] = [result.info[label]]; | ||
} | ||
result.info[label].push(value); | ||
} else { | ||
result.info[label] = value; | ||
} | ||
} | ||
} | ||
if (result.profiling) { | ||
result.profiling.push({ | ||
action: 'Finished parsing', | ||
time: Date.now() - start, | ||
}); | ||
} | ||
if (Object.keys(ntuples).length > 0) { | ||
let newNtuples = []; | ||
let keys = Object.keys(ntuples); | ||
for (let i = 0; i < keys.length; i++) { | ||
let key = keys[i]; | ||
let values = ntuples[key]; | ||
for (let j = 0; j < values.length; j++) { | ||
if (!newNtuples[j]) newNtuples[j] = {}; | ||
newNtuples[j][key] = values[j]; | ||
} | ||
} | ||
result.ntuples = newNtuples; | ||
} | ||
if (result.twoD && wantXY) { | ||
add2D(result, options); | ||
if (result.profiling) { | ||
result.profiling.push({ | ||
action: 'Finished countour plot calculation', | ||
time: Date.now() - start, | ||
}); | ||
} | ||
if (!options.keepSpectra) { | ||
delete result.spectra; | ||
} | ||
} | ||
if (options.chromatogram) { | ||
options.xy = true; | ||
} | ||
if (options.xy && wantXY) { | ||
// the spectraData should not be a oneD array but an object with x and y | ||
if (spectra.length > 0) { | ||
for (let i = 0; i < spectra.length; i++) { | ||
spectrum = spectra[i]; | ||
if (spectrum.data && spectrum.data.length > 0) { | ||
for (let j = 0; j < spectrum.data.length; j++) { | ||
let data = spectrum.data[j]; | ||
let newData = { | ||
x: new Array(data.length / 2), | ||
y: new Array(data.length / 2), | ||
}; | ||
for (let k = 0; k < data.length; k = k + 2) { | ||
newData.x[k / 2] = data[k]; | ||
newData.y[k / 2] = data[k + 1]; | ||
} | ||
spectrum.data[j] = newData; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
// maybe it is a GC (HPLC) / MS. In this case we add a new format | ||
if (options.chromatogram) { | ||
if (result.spectra.length > 1) { | ||
complexChromatogram(result); | ||
} else { | ||
simpleChromatogram(result); | ||
} | ||
if (result.profiling) { | ||
result.profiling.push({ | ||
action: 'Finished chromatogram calculation', | ||
time: Date.now() - start, | ||
}); | ||
} | ||
} | ||
if (result.profiling) { | ||
result.profiling.push({ | ||
action: 'Total time', | ||
time: Date.now() - start, | ||
}); | ||
} | ||
return result; | ||
} | ||
function convertMSFieldToLabel(value) { | ||
return value.toLowerCase().replace(/[^a-z0-9]/g, ''); | ||
} | ||
function isMSField(canonicDataLabel) { | ||
return GC_MS_FIELDS.indexOf(canonicDataLabel) !== -1; | ||
} | ||
function complexChromatogram(result) { | ||
let spectra = result.spectra; | ||
let length = spectra.length; | ||
let chromatogram = { | ||
times: new Array(length), | ||
series: { | ||
ms: { | ||
dimension: 2, | ||
data: new Array(length), | ||
}, | ||
}, | ||
}; | ||
let existingGCMSFields = []; | ||
for (let i = 0; i < GC_MS_FIELDS.length; i++) { | ||
let label = convertMSFieldToLabel(GC_MS_FIELDS[i]); | ||
if (spectra[0][label]) { | ||
existingGCMSFields.push(label); | ||
chromatogram.series[label] = { | ||
dimension: 1, | ||
data: new Array(length), | ||
}; | ||
} | ||
} | ||
for (let i = 0; i < length; i++) { | ||
let spectrum = spectra[i]; | ||
chromatogram.times[i] = spectrum.pageValue; | ||
for (let j = 0; j < existingGCMSFields.length; j++) { | ||
chromatogram.series[existingGCMSFields[j]].data[i] = parseFloat( | ||
spectrum[existingGCMSFields[j]], | ||
); | ||
} | ||
if (spectrum.data) { | ||
chromatogram.series.ms.data[i] = [ | ||
spectrum.data[0].x, | ||
spectrum.data[0].y, | ||
]; | ||
} | ||
} | ||
result.chromatogram = chromatogram; | ||
} | ||
function simpleChromatogram(result) { | ||
let data = result.spectra[0].data[0]; | ||
result.chromatogram = { | ||
times: data.x.slice(), | ||
series: { | ||
intensity: { | ||
dimension: 1, | ||
data: data.y.slice(), | ||
}, | ||
}, | ||
}; | ||
} | ||
function prepareSpectrum(result, spectrum) { | ||
if (!spectrum.xFactor) spectrum.xFactor = 1; | ||
if (!spectrum.yFactor) spectrum.yFactor = 1; | ||
if (spectrum.observeFrequency) { | ||
if (spectrum.xUnit && spectrum.xUnit.toUpperCase() === 'HZ') { | ||
spectrum.xUnit = 'PPM'; | ||
spectrum.xFactor = spectrum.xFactor / spectrum.observeFrequency; | ||
spectrum.firstX = spectrum.firstX / spectrum.observeFrequency; | ||
spectrum.lastX = spectrum.lastX / spectrum.observeFrequency; | ||
spectrum.deltaX = spectrum.deltaX / spectrum.observeFrequency; | ||
} | ||
} | ||
if (spectrum.shiftOffsetVal) { | ||
let shift = spectrum.firstX - spectrum.shiftOffsetVal; | ||
spectrum.firstX = spectrum.firstX - shift; | ||
spectrum.lastX = spectrum.lastX - shift; | ||
} | ||
} | ||
function getMedian(data) { | ||
data = data.sort(compareNumbers); | ||
let l = data.length; | ||
return data[Math.floor(l / 2)]; | ||
} | ||
function compareNumbers(a, b) { | ||
return a - b; | ||
} | ||
function convertTo3DZ(spectra) { | ||
let minZ = spectra[0].data[0][0]; | ||
let maxZ = minZ; | ||
let ySize = spectra.length; | ||
let xSize = spectra[0].data[0].length / 2; | ||
let z = new Array(ySize); | ||
for (let i = 0; i < ySize; i++) { | ||
z[i] = new Array(xSize); | ||
let xVector = spectra[i].data[0]; | ||
for (let j = 0; j < xSize; j++) { | ||
let value = xVector[j * 2 + 1]; | ||
z[i][j] = value; | ||
if (value < minZ) minZ = value; | ||
if (value > maxZ) maxZ = value; | ||
} | ||
} | ||
const firstX = spectra[0].data[0][0]; | ||
const lastX = spectra[0].data[0][spectra[0].data[0].length - 2]; // has to be -2 because it is a 1D array [x,y,x,y,...] | ||
const firstY = spectra[0].pageValue; | ||
const lastY = spectra[ySize - 1].pageValue; | ||
// Because the min / max value are the only information about the matrix if we invert | ||
// min and max we need to invert the array | ||
if (firstX > lastX) { | ||
for (let spectrum of z) { | ||
spectrum.reverse(); | ||
} | ||
} | ||
if (firstY > lastY) { | ||
z.reverse(); | ||
} | ||
return { | ||
z: z, | ||
minX: Math.min(firstX, lastX), | ||
maxX: Math.max(firstX, lastX), | ||
minY: Math.min(firstY, lastY), | ||
maxY: Math.max(firstY, lastY), | ||
minZ: minZ, | ||
maxZ: maxZ, | ||
noise: getMedian(z[0].map(Math.abs)), | ||
}; | ||
} | ||
function add2D(result, options) { | ||
let zData = convertTo3DZ(result.spectra); | ||
if (!options.noContour) { | ||
result.contourLines = generateContourLines(zData, options); | ||
delete zData.z; | ||
} | ||
result.minMax = zData; | ||
} | ||
function generateContourLines(zData, options) { | ||
let noise = zData.noise; | ||
let z = zData.z; | ||
let povarHeight0, povarHeight1, povarHeight2, povarHeight3; | ||
let isOver0, isOver1, isOver2, isOver3; | ||
let nbSubSpectra = z.length; | ||
let nbPovars = z[0].length; | ||
let pAx, pAy, pBx, pBy; | ||
let x0 = zData.minX; | ||
let xN = zData.maxX; | ||
let dx = (xN - x0) / (nbPovars - 1); | ||
let y0 = zData.minY; | ||
let yN = zData.maxY; | ||
let dy = (yN - y0) / (nbSubSpectra - 1); | ||
let minZ = zData.minZ; | ||
let maxZ = zData.maxZ; | ||
// System.out.prvarln('y0 '+y0+' yN '+yN); | ||
// ------------------------- | ||
// Povars attribution | ||
// | ||
// 0----1 | ||
// | / | | ||
// | / | | ||
// 2----3 | ||
// | ||
// ---------------------d------ | ||
let iter = options.nbContourLevels * 2; | ||
let contourLevels = new Array(iter); | ||
let lineZValue; | ||
for (let level = 0; level < iter; level++) { | ||
// multiply by 2 for positif and negatif | ||
let contourLevel = {}; | ||
contourLevels[level] = contourLevel; | ||
let side = level % 2; | ||
let factor = | ||
(maxZ - options.noiseMultiplier * noise) * | ||
Math.exp((level >> 1) - options.nbContourLevels); | ||
if (side === 0) { | ||
lineZValue = factor + options.noiseMultiplier * noise; | ||
} else { | ||
lineZValue = 0 - factor - options.noiseMultiplier * noise; | ||
} | ||
let lines = []; | ||
contourLevel.zValue = lineZValue; | ||
contourLevel.lines = lines; | ||
if (lineZValue <= minZ || lineZValue >= maxZ) continue; | ||
for (let iSubSpectra = 0; iSubSpectra < nbSubSpectra - 1; iSubSpectra++) { | ||
let subSpectra = z[iSubSpectra]; | ||
let subSpectraAfter = z[iSubSpectra + 1]; | ||
for (let povar = 0; povar < nbPovars - 1; povar++) { | ||
povarHeight0 = subSpectra[povar]; | ||
povarHeight1 = subSpectra[povar + 1]; | ||
povarHeight2 = subSpectraAfter[povar]; | ||
povarHeight3 = subSpectraAfter[povar + 1]; | ||
isOver0 = povarHeight0 > lineZValue; | ||
isOver1 = povarHeight1 > lineZValue; | ||
isOver2 = povarHeight2 > lineZValue; | ||
isOver3 = povarHeight3 > lineZValue; | ||
// Example povar0 is over the plane and povar1 and | ||
// povar2 are below, we find the varersections and add | ||
// the segment | ||
if (isOver0 !== isOver1 && isOver0 !== isOver2) { | ||
pAx = | ||
povar + | ||
(lineZValue - povarHeight0) / (povarHeight1 - povarHeight0); | ||
pAy = iSubSpectra; | ||
pBx = povar; | ||
pBy = | ||
iSubSpectra + | ||
(lineZValue - povarHeight0) / (povarHeight2 - povarHeight0); | ||
lines.push(pAx * dx + x0); | ||
lines.push(pAy * dy + y0); | ||
lines.push(pBx * dx + x0); | ||
lines.push(pBy * dy + y0); | ||
} | ||
// remove push does not help !!!! | ||
if (isOver3 !== isOver1 && isOver3 !== isOver2) { | ||
pAx = povar + 1; | ||
pAy = | ||
iSubSpectra + | ||
1 - | ||
(lineZValue - povarHeight3) / (povarHeight1 - povarHeight3); | ||
pBx = | ||
povar + | ||
1 - | ||
(lineZValue - povarHeight3) / (povarHeight2 - povarHeight3); | ||
pBy = iSubSpectra + 1; | ||
lines.push(pAx * dx + x0); | ||
lines.push(pAy * dy + y0); | ||
lines.push(pBx * dx + x0); | ||
lines.push(pBy * dy + y0); | ||
} | ||
// test around the diagonal | ||
if (isOver1 !== isOver2) { | ||
pAx = | ||
(povar + | ||
1 - | ||
(lineZValue - povarHeight1) / (povarHeight2 - povarHeight1)) * | ||
dx + | ||
x0; | ||
pAy = | ||
(iSubSpectra + | ||
(lineZValue - povarHeight1) / (povarHeight2 - povarHeight1)) * | ||
dy + | ||
y0; | ||
if (isOver1 !== isOver0) { | ||
pBx = | ||
povar + | ||
1 - | ||
(lineZValue - povarHeight1) / (povarHeight0 - povarHeight1); | ||
pBy = iSubSpectra; | ||
lines.push(pAx); | ||
lines.push(pAy); | ||
lines.push(pBx * dx + x0); | ||
lines.push(pBy * dy + y0); | ||
} | ||
if (isOver2 !== isOver0) { | ||
pBx = povar; | ||
pBy = | ||
iSubSpectra + | ||
1 - | ||
(lineZValue - povarHeight2) / (povarHeight0 - povarHeight2); | ||
lines.push(pAx); | ||
lines.push(pAy); | ||
lines.push(pBx * dx + x0); | ||
lines.push(pBy * dy + y0); | ||
} | ||
if (isOver1 !== isOver3) { | ||
pBx = povar + 1; | ||
pBy = | ||
iSubSpectra + | ||
(lineZValue - povarHeight1) / (povarHeight3 - povarHeight1); | ||
lines.push(pAx); | ||
lines.push(pAy); | ||
lines.push(pBx * dx + x0); | ||
lines.push(pBy * dy + y0); | ||
} | ||
if (isOver2 !== isOver3) { | ||
pBx = | ||
povar + | ||
(lineZValue - povarHeight2) / (povarHeight3 - povarHeight2); | ||
pBy = iSubSpectra + 1; | ||
lines.push(pAx); | ||
lines.push(pAy); | ||
lines.push(pBx * dx + x0); | ||
lines.push(pBy * dy + y0); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return { | ||
minX: zData.minX, | ||
maxX: zData.maxX, | ||
minY: zData.minY, | ||
maxY: zData.maxY, | ||
segments: contourLevels, | ||
}; | ||
} | ||
function fastParseXYData(spectrum, value) { | ||
// TODO need to deal with result | ||
// console.log(value); | ||
// we check if deltaX is defined otherwise we calculate it | ||
let yFactor = spectrum.yFactor; | ||
let deltaX = spectrum.deltaX; | ||
spectrum.isXYdata = true; | ||
// TODO to be improved using 2 array {x:[], y:[]} | ||
let currentData = []; | ||
spectrum.data = [currentData]; | ||
let currentX = spectrum.firstX; | ||
let currentY = spectrum.firstY; | ||
// we skip the first line | ||
// | ||
let endLine = false; | ||
let ascii; | ||
let i = 0; | ||
for (; i < value.length; i++) { | ||
ascii = value.charCodeAt(i); | ||
if (ascii === 13 || ascii === 10) { | ||
endLine = true; | ||
} else { | ||
if (endLine) break; | ||
} | ||
} | ||
// we proceed taking the i after the first line | ||
let newLine = true; | ||
let isDifference = false; | ||
let isLastDifference = false; | ||
let lastDifference = 0; | ||
let isDuplicate = false; | ||
let inComment = false; | ||
let currentValue = 0; // can be a difference or a duplicate | ||
let lastValue = 0; // must be the real last value | ||
let isNegative = false; | ||
let inValue = false; | ||
let skipFirstValue = false; | ||
let decimalPosition = 0; | ||
for (; i <= value.length; i++) { | ||
if (i === value.length) ascii = 13; | ||
else ascii = value.charCodeAt(i); | ||
if (inComment) { | ||
// we should ignore the text if we are after $$ | ||
if (ascii === 13 || ascii === 10) { | ||
newLine = true; | ||
inComment = false; | ||
} | ||
} else { | ||
// when is it a new value ? | ||
// when it is not a digit, . or comma | ||
// it is a number that is either new or we continue | ||
if (ascii <= 57 && ascii >= 48) { | ||
// a number | ||
inValue = true; | ||
if (decimalPosition > 0) { | ||
currentValue += (ascii - 48) / Math.pow(10, decimalPosition++); | ||
} else { | ||
currentValue *= 10; | ||
currentValue += ascii - 48; | ||
} | ||
} else if (ascii === 44 || ascii === 46) { | ||
// a "," or "." | ||
inValue = true; | ||
decimalPosition++; | ||
} else { | ||
if (inValue) { | ||
// need to process the previous value | ||
if (newLine) { | ||
newLine = false; // we don't check the X value | ||
// console.log("NEW LINE",isDifference, lastDifference); | ||
// if new line and lastDifference, the first value is just a check ! | ||
// that we don't check ... | ||
if (isLastDifference) skipFirstValue = true; | ||
} else { | ||
// need to deal with duplicate and differences | ||
if (skipFirstValue) { | ||
skipFirstValue = false; | ||
} else { | ||
if (isDifference) { | ||
lastDifference = isNegative ? 0 - currentValue : currentValue; | ||
isLastDifference = true; | ||
isDifference = false; | ||
} else if (!isDuplicate) { | ||
lastValue = isNegative ? 0 - currentValue : currentValue; | ||
} | ||
let duplicate = isDuplicate ? currentValue - 1 : 1; | ||
for (let j = 0; j < duplicate; j++) { | ||
if (isLastDifference) { | ||
currentY += lastDifference; | ||
} else { | ||
currentY = lastValue; | ||
} | ||
currentData.push(currentX); | ||
currentData.push(currentY * yFactor); | ||
currentX += deltaX; | ||
} | ||
} | ||
} | ||
isNegative = false; | ||
currentValue = 0; | ||
decimalPosition = 0; | ||
inValue = false; | ||
isDuplicate = false; | ||
} | ||
// positive SQZ digits @ A B C D E F G H I (ascii 64-73) | ||
if (ascii < 74 && ascii > 63) { | ||
inValue = true; | ||
isLastDifference = false; | ||
currentValue = ascii - 64; | ||
} else if (ascii > 96 && ascii < 106) { | ||
// negative SQZ digits a b c d e f g h i (ascii 97-105) | ||
inValue = true; | ||
isLastDifference = false; | ||
currentValue = ascii - 96; | ||
isNegative = true; | ||
} else if (ascii === 115) { | ||
// DUP digits S T U V W X Y Z s (ascii 83-90, 115) | ||
inValue = true; | ||
isDuplicate = true; | ||
currentValue = 9; | ||
} else if (ascii > 82 && ascii < 91) { | ||
inValue = true; | ||
isDuplicate = true; | ||
currentValue = ascii - 82; | ||
} else if (ascii > 73 && ascii < 83) { | ||
// positive DIF digits % J K L M N O P Q R (ascii 37, 74-82) | ||
inValue = true; | ||
isDifference = true; | ||
currentValue = ascii - 73; | ||
} else if (ascii > 105 && ascii < 115) { | ||
// negative DIF digits j k l m n o p q r (ascii 106-114) | ||
inValue = true; | ||
isDifference = true; | ||
currentValue = ascii - 105; | ||
isNegative = true; | ||
} else if (ascii === 36 && value.charCodeAt(i + 1) === 36) { | ||
// $ sign, we need to check the next one | ||
inValue = true; | ||
inComment = true; | ||
} else if (ascii === 37) { | ||
// positive DIF digits % J K L M N O P Q R (ascii 37, 74-82) | ||
inValue = true; | ||
isDifference = true; | ||
currentValue = 0; | ||
isNegative = false; | ||
} else if (ascii === 45) { | ||
// a "-" | ||
// check if after there is a number, decimal or comma | ||
let ascii2 = value.charCodeAt(i + 1); | ||
if ( | ||
(ascii2 >= 48 && ascii2 <= 57) || | ||
ascii2 === 44 || | ||
ascii2 === 46 | ||
) { | ||
inValue = true; | ||
if (!newLine) isLastDifference = false; | ||
isNegative = true; | ||
} | ||
} else if (ascii === 13 || ascii === 10) { | ||
newLine = true; | ||
inComment = false; | ||
} | ||
// and now analyse the details ... space or tabulation | ||
// if "+" we just don't care | ||
} | ||
} | ||
} | ||
} | ||
function parseXYA(spectrum, value) { | ||
let removeSymbolRegExp = /(\(+|\)+|<+|>+|\s+)/g; | ||
spectrum.isXYAdata = true; | ||
let values; | ||
let currentData = []; | ||
spectrum.data = [currentData]; | ||
let lines = value.split(/,? *,?[;\r\n]+ */); | ||
for (let i = 1; i < lines.length; i++) { | ||
values = lines[i].trim().replace(removeSymbolRegExp, '').split(','); | ||
currentData.push(parseFloat(values[0])); | ||
currentData.push(parseFloat(values[1])); | ||
} | ||
} | ||
function parsePeakTable(spectrum, value, result) { | ||
let removeCommentRegExp = /\$\$.*/; | ||
let peakTableSplitRegExp = /[,\t ]+/; | ||
spectrum.isPeaktable = true; | ||
let values; | ||
let currentData = []; | ||
spectrum.data = [currentData]; | ||
// counts for around 20% of the time | ||
let lines = value.split(/,? *,?[;\r\n]+ */); | ||
for (let i = 1; i < lines.length; i++) { | ||
values = lines[i] | ||
.trim() | ||
.replace(removeCommentRegExp, '') | ||
.split(peakTableSplitRegExp); | ||
if (values.length % 2 === 0) { | ||
for (let j = 0; j < values.length; j = j + 2) { | ||
// takes around 40% of the time to add and parse the 2 values nearly exclusively because of parseFloat | ||
currentData.push(parseFloat(values[j]) * spectrum.xFactor); | ||
currentData.push(parseFloat(values[j + 1]) * spectrum.yFactor); | ||
} | ||
} else { | ||
result.logs.push(`Format error: ${values}`); | ||
} | ||
} | ||
} | ||
return convert; | ||
} | ||
let convert = getConverter(); | ||
function JcampConverter(input, options, useWorker) { | ||
if (typeof options === 'boolean') { | ||
useWorker = options; | ||
options = {}; | ||
} | ||
if (useWorker) { | ||
return postToWorker(input, options); | ||
} else { | ||
return convert(input, options); | ||
} | ||
} | ||
let stamps = {}; | ||
let worker; | ||
function postToWorker(input, options) { | ||
if (!worker) { | ||
createWorker(); | ||
} | ||
return new Promise(function (resolve) { | ||
let stamp = `${Date.now()}${Math.random()}`; | ||
stamps[stamp] = resolve; | ||
worker.postMessage( | ||
JSON.stringify({ | ||
stamp: stamp, | ||
input: input, | ||
options: options, | ||
}), | ||
); | ||
}); | ||
} | ||
function createWorker() { | ||
let workerURL = URL.createObjectURL( | ||
new Blob( | ||
[ | ||
`var getConverter =${getConverter.toString()};var convert = getConverter(); onmessage = function (event) { var data = JSON.parse(event.data); postMessage(JSON.stringify({stamp: data.stamp, output: convert(data.input, data.options)})); };`, | ||
], | ||
{ type: 'application/javascript' }, | ||
), | ||
); | ||
worker = new Worker(workerURL); | ||
URL.revokeObjectURL(workerURL); | ||
worker.addEventListener('message', function (event) { | ||
let data = JSON.parse(event.data); | ||
let stamp = data.stamp; | ||
if (stamps[stamp]) { | ||
stamps[stamp](data.output); | ||
} | ||
}); | ||
} | ||
function createTree(jcamp, options = {}) { | ||
const { flatten = false } = options; | ||
if (typeof jcamp !== 'string') { | ||
throw new TypeError('the JCAMP should be a string'); | ||
} | ||
let lines = jcamp.split(/[\r\n]+/); | ||
let flat = []; | ||
let stack = []; | ||
let result = []; | ||
let current; | ||
let ntupleLevel = 0; | ||
let spaces = jcamp.includes('## '); | ||
for (let i = 0; i < lines.length; i++) { | ||
let line = lines[i]; | ||
let labelLine = spaces ? line.replace(/ /g, '') : line; | ||
if (labelLine.substring(0, 9) === '##NTUPLES') { | ||
ntupleLevel++; | ||
} | ||
if (labelLine.substring(0, 7) === '##TITLE') { | ||
let title = [labelLine.substring(8).trim()]; | ||
for (let j = i + 1; j < lines.length; j++) { | ||
if (lines[j].startsWith('##')) { | ||
break; | ||
} else { | ||
title.push(lines[j].trim()); | ||
} | ||
} | ||
stack.push({ | ||
title: title.join('\n'), | ||
jcamp: `${line}\n`, | ||
children: [], | ||
}); | ||
current = stack[stack.length - 1]; | ||
flat.push(current); | ||
} else if (labelLine.substring(0, 5) === '##END' && ntupleLevel === 0) { | ||
current.jcamp += `${line}\n`; | ||
let finished = stack.pop(); | ||
if (stack.length !== 0) { | ||
current = stack[stack.length - 1]; | ||
current.children.push(finished); | ||
} else { | ||
current = undefined; | ||
result.push(finished); | ||
} | ||
} else if (current && current.jcamp) { | ||
current.jcamp += `${line}\n`; | ||
let match = labelLine.match(/^##(.*?)=(.+)/); | ||
if (match) { | ||
let canonicDataLabel = match[1].replace(/[ _-]/g, '').toUpperCase(); | ||
if (canonicDataLabel === 'DATATYPE') { | ||
current.dataType = match[2].trim(); | ||
} | ||
} | ||
} | ||
if (labelLine.substring(0, 5) === '##END' && ntupleLevel > 0) { | ||
ntupleLevel--; | ||
} | ||
} | ||
if (flatten) { | ||
flat.forEach((entry) => { | ||
entry.children = undefined; | ||
}); | ||
return flat; | ||
} else { | ||
return result; | ||
} | ||
} | ||
module.exports = { | ||
convert: JcampConverter, | ||
createTree: createTree, | ||
}; | ||
export { default as createTree } from './createTree'; | ||
export { default as convert } from './convert'; |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
77454
20
1922
108
1
13
1
+ Addedml-array-median@^1.1.3
+ Addedis-any-array@2.0.1(transitive)
+ Addedmedian-quickselect@1.0.1(transitive)
+ Addedml-array-median@1.1.6(transitive)