@complat/react-spectra-editor 0.10.13-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +662 -0
- package/README.md +37 -0
- package/dist/actions/edit_peak.js +25 -0
- package/dist/actions/forecast.js +41 -0
- package/dist/actions/integration.js +33 -0
- package/dist/actions/jcamp.js +41 -0
- package/dist/actions/layout.js +17 -0
- package/dist/actions/manager.js +49 -0
- package/dist/actions/meta.js +17 -0
- package/dist/actions/multiplicity.js +57 -0
- package/dist/actions/scan.js +33 -0
- package/dist/actions/shift.js +25 -0
- package/dist/actions/status.js +33 -0
- package/dist/actions/submit.js +41 -0
- package/dist/actions/threshold.js +33 -0
- package/dist/actions/ui.js +50 -0
- package/dist/app.js +125 -0
- package/dist/components/cmd_bar/01_viewer.js +133 -0
- package/dist/components/cmd_bar/02_zoom.js +119 -0
- package/dist/components/cmd_bar/03_peak.js +176 -0
- package/dist/components/cmd_bar/04_integration.js +273 -0
- package/dist/components/cmd_bar/05_multiplicity.js +228 -0
- package/dist/components/cmd_bar/06_undo_redo.js +137 -0
- package/dist/components/cmd_bar/common.js +104 -0
- package/dist/components/cmd_bar/index.js +113 -0
- package/dist/components/cmd_bar/r01_layout.js +351 -0
- package/dist/components/cmd_bar/r02_scan.js +226 -0
- package/dist/components/cmd_bar/r03_threshold.js +209 -0
- package/dist/components/cmd_bar/r04_submit.js +349 -0
- package/dist/components/cmd_bar/r05_submit_btn.js +147 -0
- package/dist/components/cmd_bar/r06_predict_btn.js +307 -0
- package/dist/components/cmd_bar/tri_btn.js +202 -0
- package/dist/components/common/chem.js +115 -0
- package/dist/components/common/comps.js +29 -0
- package/dist/components/common/draw.js +41 -0
- package/dist/components/d3_line/index.js +236 -0
- package/dist/components/d3_line/line_focus.js +765 -0
- package/dist/components/d3_rect/index.js +200 -0
- package/dist/components/d3_rect/rect_focus.js +301 -0
- package/dist/components/forecast/comps.js +337 -0
- package/dist/components/forecast/ir_comps.js +224 -0
- package/dist/components/forecast/ir_viewer.js +172 -0
- package/dist/components/forecast/nmr_comps.js +253 -0
- package/dist/components/forecast/nmr_viewer.js +170 -0
- package/dist/components/forecast/section_loading.js +95 -0
- package/dist/components/forecast_viewer.js +190 -0
- package/dist/components/panel/compare.js +370 -0
- package/dist/components/panel/index.js +191 -0
- package/dist/components/panel/info.js +335 -0
- package/dist/components/panel/multiplicity.js +405 -0
- package/dist/components/panel/multiplicity_coupling.js +195 -0
- package/dist/components/panel/multiplicity_select.js +114 -0
- package/dist/components/panel/peaks.js +296 -0
- package/dist/constants/action_type.js +140 -0
- package/dist/constants/list_layout.js +23 -0
- package/dist/constants/list_shift.js +480 -0
- package/dist/constants/list_ui.js +33 -0
- package/dist/fn.js +31 -0
- package/dist/helpers/brush.js +109 -0
- package/dist/helpers/calc.js +10 -0
- package/dist/helpers/carbonFeatures.js +47 -0
- package/dist/helpers/cfg.js +89 -0
- package/dist/helpers/chem.js +594 -0
- package/dist/helpers/compass.js +91 -0
- package/dist/helpers/converter.js +74 -0
- package/dist/helpers/extractParams.js +77 -0
- package/dist/helpers/extractPeaksEdit.js +69 -0
- package/dist/helpers/focus.js +15 -0
- package/dist/helpers/format.js +403 -0
- package/dist/helpers/init.js +80 -0
- package/dist/helpers/integration.js +30 -0
- package/dist/helpers/mount.js +112 -0
- package/dist/helpers/multiplicity.js +44 -0
- package/dist/helpers/multiplicity_calc.js +117 -0
- package/dist/helpers/multiplicity_complat.js +126 -0
- package/dist/helpers/multiplicity_manual.js +94 -0
- package/dist/helpers/multiplicity_verify_basic.js +196 -0
- package/dist/helpers/shift.js +48 -0
- package/dist/helpers/zoom.js +32 -0
- package/dist/index.js +705 -0
- package/dist/layer_content.js +125 -0
- package/dist/layer_init.js +231 -0
- package/dist/layer_prism.js +186 -0
- package/dist/reducers/index.js +89 -0
- package/dist/reducers/reducer_edit_peak.js +111 -0
- package/dist/reducers/reducer_forecast.js +113 -0
- package/dist/reducers/reducer_integration.js +136 -0
- package/dist/reducers/reducer_jcamp.js +74 -0
- package/dist/reducers/reducer_layout.js +27 -0
- package/dist/reducers/reducer_manager.js +20 -0
- package/dist/reducers/reducer_meta.js +30 -0
- package/dist/reducers/reducer_multiplicity.js +131 -0
- package/dist/reducers/reducer_scan.js +55 -0
- package/dist/reducers/reducer_shift.js +99 -0
- package/dist/reducers/reducer_simulation.js +30 -0
- package/dist/reducers/reducer_status.js +41 -0
- package/dist/reducers/reducer_submit.js +54 -0
- package/dist/reducers/reducer_threshold.js +34 -0
- package/dist/reducers/reducer_ui.js +46 -0
- package/dist/reducers/undo_redo_config.js +24 -0
- package/dist/sagas/index.js +50 -0
- package/dist/sagas/saga_edit_peak.js +84 -0
- package/dist/sagas/saga_manager.js +116 -0
- package/dist/sagas/saga_meta.js +46 -0
- package/dist/sagas/saga_multiplicity.js +387 -0
- package/dist/sagas/saga_ui.js +392 -0
- package/dist/third_party/jAnalyzer.js +596 -0
- package/dist/third_party/peakInterval.js +107 -0
- package/package.json +77 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
// https://github.com/cheminfo-js/spectra/blob/master/packages/spectra-data/src/peakPicking/jAnalyzer.js
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* This library implements the J analyser described by Cobas et al in the paper:
|
|
10
|
+
* A two-stage approach to automatic determination of 1H NMR coupling constants
|
|
11
|
+
*/
|
|
12
|
+
var patterns = ['s', 'd', 't', 'q', 'quint', 'h', 'sept', 'o', 'n'];
|
|
13
|
+
var symRatio = 1.5;
|
|
14
|
+
var maxErrorIter1 = 2.5; // Hz
|
|
15
|
+
var maxErrorIter2 = 1; // Hz
|
|
16
|
+
|
|
17
|
+
exports.default = {
|
|
18
|
+
/**
|
|
19
|
+
* The compilation process implements at the first stage a normalization procedure described by Golotvin et al.
|
|
20
|
+
* embedding in peak-component-counting method described by Hoyes et al.
|
|
21
|
+
* @param {object} signal
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
compilePattern: function compilePattern(signal) {
|
|
25
|
+
signal.multiplicity = 'm';
|
|
26
|
+
// 1.1 symmetrize
|
|
27
|
+
// It will add a set of peaks(signal.peaksComp) to the signal that will be used during
|
|
28
|
+
// the compilation process. The unit of those peaks will be in Hz
|
|
29
|
+
signal.symRank = symmetrizeChoiseBest(signal, maxErrorIter1, 1);
|
|
30
|
+
signal.asymmetric = true;
|
|
31
|
+
// Is the signal symmetric?
|
|
32
|
+
if (signal.symRank >= 0.95 && signal.peaksComp.length < 32) {
|
|
33
|
+
signal.asymmetric = false;
|
|
34
|
+
var i, j, n, P1, n2, maxFlagged;
|
|
35
|
+
var k = 1;
|
|
36
|
+
var Jc = [];
|
|
37
|
+
|
|
38
|
+
// Loop over the possible number of coupling contributing to the multiplet
|
|
39
|
+
for (n = 0; n < 9; n++) {
|
|
40
|
+
// 1.2 Normalize. It makes a deep copy of the peaks before to modify them.
|
|
41
|
+
var peaks = normalize(signal, n);
|
|
42
|
+
// signal.peaksCompX = peaks;
|
|
43
|
+
var validPattern = false; // It will change to true, when we find the good patter
|
|
44
|
+
// Lets check if the signal could be a singulet.
|
|
45
|
+
if (peaks.length === 1 && n === 0) {
|
|
46
|
+
validPattern = true;
|
|
47
|
+
} else {
|
|
48
|
+
if (peaks.length <= 1) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 1.3 Establish a range for the Heights Hi [peaks.intensity*0.85,peaks.intensity*1.15];
|
|
53
|
+
var ranges = getRanges(peaks);
|
|
54
|
+
n2 = Math.pow(2, n);
|
|
55
|
+
|
|
56
|
+
// 1.4 Find a combination of integer heights Hi, one from each Si, that sums to 2^n.
|
|
57
|
+
var heights = null;
|
|
58
|
+
var counter = 1;
|
|
59
|
+
while (!validPattern && (heights = getNextCombination(ranges, n2)) !== null && counter < 400) {
|
|
60
|
+
// 2.1 Number the components of the multiplet consecutively from 1 to 2n,
|
|
61
|
+
// starting at peak 1
|
|
62
|
+
var numbering = new Array(heights.length);
|
|
63
|
+
k = 1;
|
|
64
|
+
for (i = 0; i < heights.length; i++) {
|
|
65
|
+
numbering[i] = new Array(heights[i]);
|
|
66
|
+
for (j = 0; j < heights[i]; j++) {
|
|
67
|
+
numbering[i][j] = k++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Jc = []; // The array to store the detected j-coupling
|
|
72
|
+
// 2.2 Set j = 1; J1 = P2 - P1. Flag components 1 and 2 as accounted for.
|
|
73
|
+
j = 1;
|
|
74
|
+
Jc.push(peaks[1].x - peaks[0].x);
|
|
75
|
+
P1 = peaks[0].x;
|
|
76
|
+
numbering[0].splice(0, 1); // Flagged
|
|
77
|
+
numbering[1].splice(0, 1); // Flagged
|
|
78
|
+
k = 1;
|
|
79
|
+
var nFlagged = 2;
|
|
80
|
+
maxFlagged = Math.pow(2, n) - 1;
|
|
81
|
+
while (Jc.length < n && nFlagged < maxFlagged && k < peaks.length) {
|
|
82
|
+
counter += 1;
|
|
83
|
+
// 4.1. Increment j. Set k to the number of the first unflagged component.
|
|
84
|
+
j++;
|
|
85
|
+
while (k < peaks.length && numbering[k].length === 0) {
|
|
86
|
+
k++;
|
|
87
|
+
}
|
|
88
|
+
if (k < peaks.length) {
|
|
89
|
+
// 4.2 Jj = Pk - P1.
|
|
90
|
+
Jc.push(peaks[k].x - peaks[0].x);
|
|
91
|
+
// Flag component k and, for each sum of the...
|
|
92
|
+
numbering[k].splice(0, 1); // Flageed
|
|
93
|
+
nFlagged++;
|
|
94
|
+
// Flag the other components of the multiplet
|
|
95
|
+
for (var u = 2; u <= j; u++) {
|
|
96
|
+
// TODO improve those loops
|
|
97
|
+
var jSum = 0;
|
|
98
|
+
for (i = 0; i < u; i++) {
|
|
99
|
+
jSum += Jc[i];
|
|
100
|
+
}
|
|
101
|
+
for (i = 1; i < numbering.length; i++) {
|
|
102
|
+
// Maybe 0.25 Hz is too much?
|
|
103
|
+
if (Math.abs(peaks[i].x - (P1 + jSum)) < 0.25) {
|
|
104
|
+
numbering[i].splice(0, 1); // Flageed
|
|
105
|
+
nFlagged++;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Calculate the ideal patter by using the extracted j-couplings
|
|
113
|
+
var pattern = idealPattern(Jc);
|
|
114
|
+
// Compare the ideal pattern with the proposed intensities.
|
|
115
|
+
// All the intensities have to match to accept the multiplet
|
|
116
|
+
validPattern = true;
|
|
117
|
+
for (i = 0; i < pattern.length; i++) {
|
|
118
|
+
if (pattern[i].intensity !== heights[i]) {
|
|
119
|
+
validPattern = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// If we found a valid pattern we should inform about the pattern.
|
|
124
|
+
if (validPattern) {
|
|
125
|
+
updateSignal(signal, Jc);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Before to return, change the units of peaksComp from Hz to PPM again
|
|
130
|
+
for (i = 0; i < signal.peaksComp.length; i++) {
|
|
131
|
+
signal.peaksComp[i].x /= signal.observe;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @private
|
|
138
|
+
* update the signal
|
|
139
|
+
* @param {*} signal
|
|
140
|
+
* @param {*} Jc
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
function updateSignal(signal, Jc) {
|
|
144
|
+
// Update the limits of the signal
|
|
145
|
+
var peaks = signal.peaksComp; // Always in Hz
|
|
146
|
+
var nbPeaks = peaks.length;
|
|
147
|
+
signal.startX = peaks[0].x / signal.observe - peaks[0].width;
|
|
148
|
+
signal.stopX = peaks[nbPeaks - 1].x / signal.observe + peaks[nbPeaks - 1].width;
|
|
149
|
+
|
|
150
|
+
// signal.integralData.from = peaks[0].x / signal.observe - peaks[0].width * 3;
|
|
151
|
+
// signal.integralData.to =
|
|
152
|
+
// peaks[nbPeaks - 1].x / signal.observe + peaks[nbPeaks - 1].width * 3;
|
|
153
|
+
|
|
154
|
+
// Compile the pattern and format the constant couplings
|
|
155
|
+
signal.maskPattern = signal.mask2;
|
|
156
|
+
signal.multiplicity = abstractPattern(signal, Jc);
|
|
157
|
+
signal.pattern = signal.multiplicity; // Our library depends on this parameter, but it is old
|
|
158
|
+
// console.log(signal);
|
|
159
|
+
/* if (DEBUG) {
|
|
160
|
+
console.log('Final j-couplings: ' + JSON.stringify(Jc));
|
|
161
|
+
}*/
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Returns the multiplet in the compact format
|
|
166
|
+
* @param {object} signal
|
|
167
|
+
* @param {object} Jc
|
|
168
|
+
* @return {string}
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
function abstractPattern(signal, Jc) {
|
|
172
|
+
var tol = 0.05;
|
|
173
|
+
var i = void 0;
|
|
174
|
+
var pattern = '';
|
|
175
|
+
var cont = 1;
|
|
176
|
+
var newNmrJs = [];
|
|
177
|
+
|
|
178
|
+
if (Jc && Jc.length > 0) {
|
|
179
|
+
Jc.sort(function (a, b) {
|
|
180
|
+
return b - a;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
for (i = 0; i < Jc.length - 1; i++) {
|
|
184
|
+
if (Math.abs(Jc[i] - Jc[i + 1]) < tol) {
|
|
185
|
+
cont++;
|
|
186
|
+
} else {
|
|
187
|
+
newNmrJs.push({
|
|
188
|
+
coupling: Math.abs(Jc[i]),
|
|
189
|
+
multiplicity: patterns[cont]
|
|
190
|
+
});
|
|
191
|
+
pattern += patterns[cont];
|
|
192
|
+
cont = 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
newNmrJs.push({ coupling: Math.abs(Jc[i]), multiplicity: patterns[cont] });
|
|
196
|
+
pattern += patterns[cont];
|
|
197
|
+
signal.nmrJs = newNmrJs;
|
|
198
|
+
} else {
|
|
199
|
+
pattern = 's';
|
|
200
|
+
// if (Math.abs(signal.startX - signal.stopX) * signal.observe > 16) {
|
|
201
|
+
// pattern = 'br s';
|
|
202
|
+
// }
|
|
203
|
+
}
|
|
204
|
+
return pattern;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* This function creates an ideal pattern from the given J-couplings
|
|
209
|
+
* @private
|
|
210
|
+
* @param {Array} Jc
|
|
211
|
+
* @return {*[]}
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
function idealPattern(Jc) {
|
|
215
|
+
var hsum = Math.pow(2, Jc.length);
|
|
216
|
+
var i = void 0,
|
|
217
|
+
j = void 0;
|
|
218
|
+
var pattern = [{ x: 0, intensity: hsum }];
|
|
219
|
+
// To split the initial height
|
|
220
|
+
for (i = 0; i < Jc.length; i++) {
|
|
221
|
+
for (j = pattern.length - 1; j >= 0; j--) {
|
|
222
|
+
pattern.push({
|
|
223
|
+
x: pattern[j].x + Jc[i] / 2,
|
|
224
|
+
intensity: pattern[j].intensity / 2
|
|
225
|
+
});
|
|
226
|
+
pattern[j].x = pattern[j].x - Jc[i] / 2;
|
|
227
|
+
pattern[j].intensity = pattern[j].intensity / 2;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// To sum the heights in the same positions
|
|
231
|
+
pattern.sort(function compare(a, b) {
|
|
232
|
+
return a.x - b.x;
|
|
233
|
+
});
|
|
234
|
+
for (j = pattern.length - 2; j >= 0; j--) {
|
|
235
|
+
if (Math.abs(pattern[j].x - pattern[j + 1].x) < 0.1) {
|
|
236
|
+
pattern[j].intensity += pattern[j + 1].intensity;
|
|
237
|
+
pattern.splice(j + 1, 1);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return pattern;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Find a combination of integer heights Hi, one from each Si, that sums to 2n.
|
|
245
|
+
* @param {object} ranges
|
|
246
|
+
* @param {number} value
|
|
247
|
+
* @return {*}
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
function getNextCombination(ranges, value) {
|
|
251
|
+
var half = Math.ceil(ranges.values.length * 0.5);
|
|
252
|
+
var lng = ranges.values.length;
|
|
253
|
+
var sum = 0;
|
|
254
|
+
var i = void 0,
|
|
255
|
+
ok = void 0;
|
|
256
|
+
while (sum !== value) {
|
|
257
|
+
// Update the indexes to point at the next possible combination
|
|
258
|
+
ok = false;
|
|
259
|
+
while (!ok) {
|
|
260
|
+
ok = true;
|
|
261
|
+
ranges.currentIndex[ranges.active]++;
|
|
262
|
+
if (ranges.currentIndex[ranges.active] >= ranges.values[ranges.active].length) {
|
|
263
|
+
// In this case, there is no more possible combinations
|
|
264
|
+
if (ranges.active + 1 === half) {
|
|
265
|
+
return null;
|
|
266
|
+
} else {
|
|
267
|
+
// If this happens we need to try the next active peak
|
|
268
|
+
ranges.currentIndex[ranges.active] = 0;
|
|
269
|
+
ok = false;
|
|
270
|
+
ranges.active++;
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
ranges.active = 0;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Sum the heights for this combination
|
|
277
|
+
sum = 0;
|
|
278
|
+
for (i = 0; i < half; i++) {
|
|
279
|
+
sum += ranges.values[i][ranges.currentIndex[i]] * 2;
|
|
280
|
+
}
|
|
281
|
+
if (ranges.values.length % 2 !== 0) {
|
|
282
|
+
sum -= ranges.values[half - 1][ranges.currentIndex[half - 1]];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// If the sum is equal to the expected value, fill the array to return
|
|
286
|
+
if (sum === value) {
|
|
287
|
+
var heights = new Array(lng);
|
|
288
|
+
for (i = 0; i < half; i++) {
|
|
289
|
+
heights[i] = ranges.values[i][ranges.currentIndex[i]];
|
|
290
|
+
heights[lng - i - 1] = ranges.values[i][ranges.currentIndex[i]];
|
|
291
|
+
}
|
|
292
|
+
return heights;
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* This function generates the possible values that each peak can contribute
|
|
299
|
+
* to the multiplet.
|
|
300
|
+
* @param {Array} peaks Array of objects with peaks information {intensity}
|
|
301
|
+
* @return {{values: Array, currentIndex: Array, active: number}}
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
function getRanges(peaks) {
|
|
305
|
+
var ranges = new Array(peaks.length);
|
|
306
|
+
var currentIndex = new Array(peaks.length);
|
|
307
|
+
var min = void 0,
|
|
308
|
+
max = void 0;
|
|
309
|
+
ranges[0] = [1];
|
|
310
|
+
ranges[peaks.length - 1] = [1];
|
|
311
|
+
currentIndex[0] = -1;
|
|
312
|
+
currentIndex[peaks.length - 1] = 0;
|
|
313
|
+
for (var i = 1; i < peaks.length - 1; i++) {
|
|
314
|
+
min = Math.round(peaks[i].intensity * 0.85);
|
|
315
|
+
max = Math.round(peaks[i].intensity * 1.15);
|
|
316
|
+
ranges[i] = [];
|
|
317
|
+
for (var j = min; j <= max; j++) {
|
|
318
|
+
ranges[i].push(j);
|
|
319
|
+
}
|
|
320
|
+
currentIndex[i] = 0;
|
|
321
|
+
}
|
|
322
|
+
return { values: ranges, currentIndex: currentIndex, active: 0 };
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Performs a symmetrization of the signal by using different aproximations to the center.
|
|
326
|
+
* It will return the result of the symmetrization that removes less peaks from the signal
|
|
327
|
+
* @param {object} signal
|
|
328
|
+
* @param {number} maxError
|
|
329
|
+
* @param {number} iteration
|
|
330
|
+
* @return {*}
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
function symmetrizeChoiseBest(signal, maxError, iteration) {
|
|
334
|
+
var symRank1 = symmetrize(signal, maxError, iteration);
|
|
335
|
+
var tmpPeaks = signal.peaksComp;
|
|
336
|
+
var tmpMask = signal.mask;
|
|
337
|
+
var cs = signal.delta1;
|
|
338
|
+
signal.delta1 = (signal.peaks[0].x + signal.peaks[signal.peaks.length - 1].x) / 2;
|
|
339
|
+
var symRank2 = symmetrize(signal, maxError, iteration);
|
|
340
|
+
if (signal.peaksComp.length > tmpPeaks.length) {
|
|
341
|
+
return symRank2;
|
|
342
|
+
} else {
|
|
343
|
+
signal.delta1 = cs;
|
|
344
|
+
signal.peaksComp = tmpPeaks;
|
|
345
|
+
signal.mask = tmpMask;
|
|
346
|
+
return symRank1;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* This function will return a set of symmetric peaks that will
|
|
352
|
+
* be the enter point for the patter compilation process.
|
|
353
|
+
* @param {object} signal
|
|
354
|
+
* @param {number} maxError
|
|
355
|
+
* @param {number} iteration
|
|
356
|
+
* @return {number}
|
|
357
|
+
* @private
|
|
358
|
+
*/
|
|
359
|
+
function symmetrize(signal, maxError, iteration) {
|
|
360
|
+
// Before to symmetrize we need to keep only the peaks that possibly conforms the multiplete
|
|
361
|
+
var max = void 0,
|
|
362
|
+
min = void 0,
|
|
363
|
+
avg = void 0,
|
|
364
|
+
ratio = void 0,
|
|
365
|
+
avgWidth = void 0,
|
|
366
|
+
i = void 0;
|
|
367
|
+
var peaks = new Array(signal.peaks.length);
|
|
368
|
+
// Make a deep copy of the peaks and convert PPM ot HZ
|
|
369
|
+
for (i = 0; i < peaks.length; i++) {
|
|
370
|
+
peaks[i] = {
|
|
371
|
+
x: signal.peaks[i].x * signal.observe,
|
|
372
|
+
intensity: signal.peaks[i].intensity,
|
|
373
|
+
width: signal.peaks[i].width
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// Join the peaks that are closer than 0.25 Hz
|
|
377
|
+
for (i = peaks.length - 2; i >= 0; i--) {
|
|
378
|
+
if (Math.abs(peaks[i].x - peaks[i + 1].x) < 0.25) {
|
|
379
|
+
peaks[i].x = peaks[i].x * peaks[i].intensity + peaks[i + 1].x * peaks[i + 1].intensity;
|
|
380
|
+
peaks[i].intensity = peaks[i].intensity + peaks[i + 1].intensity;
|
|
381
|
+
peaks[i].x /= peaks[i].intensity;
|
|
382
|
+
peaks[i].intensity /= 2;
|
|
383
|
+
peaks[i].width += peaks[i + 1].width;
|
|
384
|
+
peaks.splice(i + 1, 1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
signal.peaksComp = peaks;
|
|
388
|
+
var nbPeaks = peaks.length;
|
|
389
|
+
var mask = new Array(nbPeaks);
|
|
390
|
+
signal.mask = mask;
|
|
391
|
+
var left = 0;
|
|
392
|
+
var right = peaks.length - 1;
|
|
393
|
+
var cs = signal.delta1 * signal.observe;
|
|
394
|
+
var middle = [(peaks[0].x + peaks[nbPeaks - 1].x) / 2, 1];
|
|
395
|
+
maxError = error(Math.abs(cs - middle[0]));
|
|
396
|
+
var heightSum = 0;
|
|
397
|
+
// We try to symmetrize the extreme peaks. We consider as candidates for symmetricing those which have
|
|
398
|
+
// ratio smaller than 3
|
|
399
|
+
for (i = 0; i < nbPeaks; i++) {
|
|
400
|
+
mask[i] = true;
|
|
401
|
+
heightSum += signal.peaks[i].intensity;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
while (left <= right) {
|
|
405
|
+
mask[left] = true;
|
|
406
|
+
mask[right] = true;
|
|
407
|
+
if (left === right) {
|
|
408
|
+
if (nbPeaks > 2 && Math.abs(peaks[left].x - cs) > maxError) {
|
|
409
|
+
mask[left] = false;
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
max = Math.max(peaks[left].intensity, peaks[right].intensity);
|
|
413
|
+
min = Math.min(peaks[left].intensity, peaks[right].intensity);
|
|
414
|
+
ratio = max / min;
|
|
415
|
+
if (ratio > symRatio) {
|
|
416
|
+
if (peaks[left].intensity === min) {
|
|
417
|
+
mask[left] = false;
|
|
418
|
+
right++;
|
|
419
|
+
} else {
|
|
420
|
+
mask[right] = false;
|
|
421
|
+
left--;
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
var diffL = Math.abs(peaks[left].x - cs);
|
|
425
|
+
var diffR = Math.abs(peaks[right].x - cs);
|
|
426
|
+
|
|
427
|
+
if (Math.abs(diffL - diffR) < maxError) {
|
|
428
|
+
avg = Math.min(peaks[left].intensity, peaks[right].intensity);
|
|
429
|
+
avgWidth = Math.min(peaks[left].width, peaks[right].width);
|
|
430
|
+
peaks[left].intensity = peaks[right].intensity = avg;
|
|
431
|
+
peaks[left].width = peaks[right].width = avgWidth;
|
|
432
|
+
middle = [middle[0] + (peaks[right].x + peaks[left].x) / 2, middle[1] + 1];
|
|
433
|
+
} else {
|
|
434
|
+
if (Math.max(diffL, diffR) === diffR) {
|
|
435
|
+
mask[right] = false;
|
|
436
|
+
left--;
|
|
437
|
+
} else {
|
|
438
|
+
mask[left] = false;
|
|
439
|
+
right++;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
left++;
|
|
445
|
+
right--;
|
|
446
|
+
// Only alter cs if it is the first iteration of the sym process.
|
|
447
|
+
if (iteration === 1) {
|
|
448
|
+
cs = chemicalShift(peaks, mask);
|
|
449
|
+
// There is not more available peaks
|
|
450
|
+
if (isNaN(cs)) {
|
|
451
|
+
return 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
maxError = error(Math.abs(cs - middle[0] / middle[1]));
|
|
455
|
+
}
|
|
456
|
+
// To remove the weak peaks and recalculate the cs
|
|
457
|
+
for (i = nbPeaks - 1; i >= 0; i--) {
|
|
458
|
+
if (mask[i] === false) {
|
|
459
|
+
peaks.splice(i, 1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
cs = chemicalShift(peaks);
|
|
463
|
+
if (isNaN(cs)) {
|
|
464
|
+
return 0;
|
|
465
|
+
}
|
|
466
|
+
signal.delta1 = cs / signal.observe;
|
|
467
|
+
// Now, the peak should be symmetric in heights, but we need to know if it is symmetric in x
|
|
468
|
+
var symFactor = 0;
|
|
469
|
+
var weight = 0;
|
|
470
|
+
if (peaks.length > 1) {
|
|
471
|
+
for (i = Math.ceil(peaks.length / 2) - 1; i >= 0; i--) {
|
|
472
|
+
symFactor += (3 + Math.min(Math.abs(peaks[i].x - cs), Math.abs(peaks[peaks.length - 1 - i].x - cs))) / (3 + Math.max(Math.abs(peaks[i].x - cs), Math.abs(peaks[peaks.length - 1 - i].x - cs))) * peaks[i].intensity;
|
|
473
|
+
weight += peaks[i].intensity;
|
|
474
|
+
}
|
|
475
|
+
symFactor /= weight;
|
|
476
|
+
} else {
|
|
477
|
+
if (peaks.length === 1) {
|
|
478
|
+
symFactor = 1;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
var newSumHeights = 0;
|
|
482
|
+
for (i = 0; i < peaks.length; i++) {
|
|
483
|
+
newSumHeights += peaks[i].intensity;
|
|
484
|
+
}
|
|
485
|
+
symFactor -= (heightSum - newSumHeights) / heightSum * 0.12; // Removed peaks penalty
|
|
486
|
+
// Sometimes we need a second opinion after the first symmetrization.
|
|
487
|
+
if (symFactor > 0.8 && symFactor < 0.97 && iteration < 2) {
|
|
488
|
+
return symmetrize(signal, maxErrorIter2, 2);
|
|
489
|
+
} else {
|
|
490
|
+
// Center the given pattern at cs and symmetrize x
|
|
491
|
+
if (peaks.length > 1) {
|
|
492
|
+
var dxi = void 0;
|
|
493
|
+
for (i = Math.ceil(peaks.length / 2) - 1; i >= 0; i--) {
|
|
494
|
+
dxi = (peaks[i].x - peaks[peaks.length - 1 - i].x) / 2.0;
|
|
495
|
+
peaks[i].x = cs + dxi;
|
|
496
|
+
peaks[peaks.length - 1 - i].x = cs - dxi;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return symFactor;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Error validator
|
|
504
|
+
* @param {number} value
|
|
505
|
+
* @return {number}
|
|
506
|
+
* @private
|
|
507
|
+
*/
|
|
508
|
+
function error(value) {
|
|
509
|
+
var maxError = value * 2.5;
|
|
510
|
+
if (maxError < 0.75) {
|
|
511
|
+
maxError = 0.75;
|
|
512
|
+
}
|
|
513
|
+
if (maxError > 3) {
|
|
514
|
+
maxError = 3;
|
|
515
|
+
}
|
|
516
|
+
return maxError;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* @private
|
|
520
|
+
* 2 stages normalizarion of the peaks heights to Math.pow(2,n).
|
|
521
|
+
* Creates a new mask with the peaks that could contribute to the multiplete
|
|
522
|
+
* @param {object} signal
|
|
523
|
+
* @param {number} n
|
|
524
|
+
* @return {*}
|
|
525
|
+
*/
|
|
526
|
+
function normalize(signal, n) {
|
|
527
|
+
// Perhaps this is slow
|
|
528
|
+
var peaks = JSON.parse(JSON.stringify(signal.peaksComp));
|
|
529
|
+
var norm = 0;
|
|
530
|
+
var norm2 = 0;
|
|
531
|
+
for (var i = 0; i < peaks.length; i++) {
|
|
532
|
+
norm += peaks[i].intensity;
|
|
533
|
+
}
|
|
534
|
+
norm = Math.pow(2, n) / norm;
|
|
535
|
+
signal.mask2 = JSON.parse(JSON.stringify(signal.mask));
|
|
536
|
+
|
|
537
|
+
var index = signal.mask2.length - 1;
|
|
538
|
+
for (i = peaks.length - 1; i >= 0; i--) {
|
|
539
|
+
peaks[i].intensity *= norm;
|
|
540
|
+
while (index >= 0 && signal.mask2[index] === false) {
|
|
541
|
+
index--;
|
|
542
|
+
}
|
|
543
|
+
if (peaks[i].intensity < 0.75) {
|
|
544
|
+
peaks.splice(i, 1);
|
|
545
|
+
signal.mask2[index] = false;
|
|
546
|
+
} else {
|
|
547
|
+
norm2 += peaks[i].intensity;
|
|
548
|
+
}
|
|
549
|
+
index--;
|
|
550
|
+
}
|
|
551
|
+
norm2 = Math.pow(2, n) / norm2;
|
|
552
|
+
for (i = peaks.length - 1; i >= 0; i--) {
|
|
553
|
+
peaks[i].intensity *= norm2;
|
|
554
|
+
}
|
|
555
|
+
return peaks;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* @private
|
|
560
|
+
* Calculates the chemical shift as the weighted sum of the peaks
|
|
561
|
+
* @param {Array} peaks
|
|
562
|
+
* @param {Array} mask
|
|
563
|
+
* @return {number}
|
|
564
|
+
*/
|
|
565
|
+
function chemicalShift(peaks, mask) {
|
|
566
|
+
var sum = 0;
|
|
567
|
+
var cs = 0;
|
|
568
|
+
var i = void 0,
|
|
569
|
+
area = void 0;
|
|
570
|
+
if (mask) {
|
|
571
|
+
for (i = 0; i < peaks.length; i++) {
|
|
572
|
+
if (mask[i] === true) {
|
|
573
|
+
area = getArea(peaks[i]);
|
|
574
|
+
sum += area;
|
|
575
|
+
cs += area * peaks[i].x;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
for (i = 0; i < peaks.length; i++) {
|
|
580
|
+
area = getArea(peaks[i]);
|
|
581
|
+
sum += area;
|
|
582
|
+
cs += area * peaks[i].x;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return cs / sum;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Return the area of a Lorentzian function
|
|
590
|
+
* @param {object} peak - object with peak information
|
|
591
|
+
* @return {number}
|
|
592
|
+
* @private
|
|
593
|
+
*/
|
|
594
|
+
function getArea(peak) {
|
|
595
|
+
return Math.abs(peak.intensity * peak.width * 1.57); // 1.772453851);
|
|
596
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getPeakIntervals = undefined;
|
|
7
|
+
|
|
8
|
+
var _mlSavitzkyGolayGeneralized = require('ml-savitzky-golay-generalized');
|
|
9
|
+
|
|
10
|
+
var _mlSavitzkyGolayGeneralized2 = _interopRequireDefault(_mlSavitzkyGolayGeneralized);
|
|
11
|
+
|
|
12
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
+
|
|
14
|
+
var options = {
|
|
15
|
+
sgOptions: {
|
|
16
|
+
windowSize: 9,
|
|
17
|
+
polynomial: 3
|
|
18
|
+
},
|
|
19
|
+
minMaxRatio: 0.00025,
|
|
20
|
+
broadRatio: 0.0,
|
|
21
|
+
maxCriteria: true,
|
|
22
|
+
smoothY: true,
|
|
23
|
+
realTopDetection: false,
|
|
24
|
+
heightFactor: 0,
|
|
25
|
+
boundaries: false,
|
|
26
|
+
derivativeThreshold: -1
|
|
27
|
+
}; // https://github.com/mljs/global-spectral-deconvolution/blob/master/src/gsd.js
|
|
28
|
+
|
|
29
|
+
var getPeakIntervals = function getPeakIntervals(entity) {
|
|
30
|
+
var data = entity.spectra[0].data[0];
|
|
31
|
+
|
|
32
|
+
var X = data.x;
|
|
33
|
+
var dX = X[1] - X[0];
|
|
34
|
+
var Y = (0, _mlSavitzkyGolayGeneralized2.default)(data.y, data.x, {
|
|
35
|
+
windowSize: options.sgOptions.windowSize,
|
|
36
|
+
polynomial: options.sgOptions.polynomial,
|
|
37
|
+
derivative: 0
|
|
38
|
+
});
|
|
39
|
+
var dY = (0, _mlSavitzkyGolayGeneralized2.default)(data.y, data.x, {
|
|
40
|
+
windowSize: options.sgOptions.windowSize,
|
|
41
|
+
polynomial: options.sgOptions.polynomial,
|
|
42
|
+
derivative: 1
|
|
43
|
+
});
|
|
44
|
+
var ddY = (0, _mlSavitzkyGolayGeneralized2.default)(data.y, data.x, {
|
|
45
|
+
windowSize: options.sgOptions.windowSize,
|
|
46
|
+
polynomial: options.sgOptions.polynomial,
|
|
47
|
+
derivative: 2
|
|
48
|
+
});
|
|
49
|
+
var maxDdy = 0;
|
|
50
|
+
var maxY = 0;
|
|
51
|
+
for (var i = 0; i < Y.length; i++) {
|
|
52
|
+
if (Math.abs(ddY[i]) > maxDdy) {
|
|
53
|
+
maxDdy = Math.abs(ddY[i]);
|
|
54
|
+
}
|
|
55
|
+
if (Math.abs(Y[i]) > maxY) {
|
|
56
|
+
maxY = Math.abs(Y[i]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
var lastMax = null;
|
|
60
|
+
var lastMin = null;
|
|
61
|
+
var minddY = new Array(Y.length - 2);
|
|
62
|
+
var intervalL = new Array(Y.length);
|
|
63
|
+
var intervalR = new Array(Y.length);
|
|
64
|
+
var broadMask = new Array(Y.length - 2);
|
|
65
|
+
var minddYLen = 0;
|
|
66
|
+
var intervalLLen = 0;
|
|
67
|
+
var intervalRLen = 0;
|
|
68
|
+
var broadMaskLen = 0;
|
|
69
|
+
|
|
70
|
+
for (var _i = 1; _i < Y.length - 1; ++_i) {
|
|
71
|
+
// filter based on derivativeThreshold
|
|
72
|
+
if (Math.abs(dY[_i]) > options.derivativeThreshold) {
|
|
73
|
+
// Minimum in first derivative
|
|
74
|
+
if (dY[_i] < dY[_i - 1] && dY[_i] <= dY[_i + 1] || dY[_i] <= dY[_i - 1] && dY[_i] < dY[_i + 1]) {
|
|
75
|
+
lastMin = {
|
|
76
|
+
x: X[_i],
|
|
77
|
+
index: _i
|
|
78
|
+
};
|
|
79
|
+
if (dX > 0 && lastMax !== null) {
|
|
80
|
+
intervalL[intervalLLen++] = lastMax;
|
|
81
|
+
intervalR[intervalRLen++] = lastMin;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Maximum in first derivative
|
|
86
|
+
if (dY[_i] >= dY[_i - 1] && dY[_i] > dY[_i + 1] || dY[_i] > dY[_i - 1] && dY[_i] >= dY[_i + 1]) {
|
|
87
|
+
lastMax = {
|
|
88
|
+
x: X[_i],
|
|
89
|
+
index: _i
|
|
90
|
+
};
|
|
91
|
+
if (dX < 0 && lastMin !== null) {
|
|
92
|
+
intervalL[intervalLLen++] = lastMax;
|
|
93
|
+
intervalR[intervalRLen++] = lastMin;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Minimum in second derivative
|
|
98
|
+
if (ddY[_i] < ddY[_i - 1] && ddY[_i] < ddY[_i + 1]) {
|
|
99
|
+
// TODO should we change this to have 3 arrays ? Huge overhead creating arrays
|
|
100
|
+
minddY[minddYLen++] = _i; // ( [X[i], Y[i], i] );
|
|
101
|
+
broadMask[broadMaskLen++] = Math.abs(ddY[_i]) <= options.broadRatio * maxDdy;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { intervalL: intervalL, intervalR: intervalR };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
exports.getPeakIntervals = getPeakIntervals;
|