@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.
Files changed (109) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +37 -0
  3. package/dist/actions/edit_peak.js +25 -0
  4. package/dist/actions/forecast.js +41 -0
  5. package/dist/actions/integration.js +33 -0
  6. package/dist/actions/jcamp.js +41 -0
  7. package/dist/actions/layout.js +17 -0
  8. package/dist/actions/manager.js +49 -0
  9. package/dist/actions/meta.js +17 -0
  10. package/dist/actions/multiplicity.js +57 -0
  11. package/dist/actions/scan.js +33 -0
  12. package/dist/actions/shift.js +25 -0
  13. package/dist/actions/status.js +33 -0
  14. package/dist/actions/submit.js +41 -0
  15. package/dist/actions/threshold.js +33 -0
  16. package/dist/actions/ui.js +50 -0
  17. package/dist/app.js +125 -0
  18. package/dist/components/cmd_bar/01_viewer.js +133 -0
  19. package/dist/components/cmd_bar/02_zoom.js +119 -0
  20. package/dist/components/cmd_bar/03_peak.js +176 -0
  21. package/dist/components/cmd_bar/04_integration.js +273 -0
  22. package/dist/components/cmd_bar/05_multiplicity.js +228 -0
  23. package/dist/components/cmd_bar/06_undo_redo.js +137 -0
  24. package/dist/components/cmd_bar/common.js +104 -0
  25. package/dist/components/cmd_bar/index.js +113 -0
  26. package/dist/components/cmd_bar/r01_layout.js +351 -0
  27. package/dist/components/cmd_bar/r02_scan.js +226 -0
  28. package/dist/components/cmd_bar/r03_threshold.js +209 -0
  29. package/dist/components/cmd_bar/r04_submit.js +349 -0
  30. package/dist/components/cmd_bar/r05_submit_btn.js +147 -0
  31. package/dist/components/cmd_bar/r06_predict_btn.js +307 -0
  32. package/dist/components/cmd_bar/tri_btn.js +202 -0
  33. package/dist/components/common/chem.js +115 -0
  34. package/dist/components/common/comps.js +29 -0
  35. package/dist/components/common/draw.js +41 -0
  36. package/dist/components/d3_line/index.js +236 -0
  37. package/dist/components/d3_line/line_focus.js +765 -0
  38. package/dist/components/d3_rect/index.js +200 -0
  39. package/dist/components/d3_rect/rect_focus.js +301 -0
  40. package/dist/components/forecast/comps.js +337 -0
  41. package/dist/components/forecast/ir_comps.js +224 -0
  42. package/dist/components/forecast/ir_viewer.js +172 -0
  43. package/dist/components/forecast/nmr_comps.js +253 -0
  44. package/dist/components/forecast/nmr_viewer.js +170 -0
  45. package/dist/components/forecast/section_loading.js +95 -0
  46. package/dist/components/forecast_viewer.js +190 -0
  47. package/dist/components/panel/compare.js +370 -0
  48. package/dist/components/panel/index.js +191 -0
  49. package/dist/components/panel/info.js +335 -0
  50. package/dist/components/panel/multiplicity.js +405 -0
  51. package/dist/components/panel/multiplicity_coupling.js +195 -0
  52. package/dist/components/panel/multiplicity_select.js +114 -0
  53. package/dist/components/panel/peaks.js +296 -0
  54. package/dist/constants/action_type.js +140 -0
  55. package/dist/constants/list_layout.js +23 -0
  56. package/dist/constants/list_shift.js +480 -0
  57. package/dist/constants/list_ui.js +33 -0
  58. package/dist/fn.js +31 -0
  59. package/dist/helpers/brush.js +109 -0
  60. package/dist/helpers/calc.js +10 -0
  61. package/dist/helpers/carbonFeatures.js +47 -0
  62. package/dist/helpers/cfg.js +89 -0
  63. package/dist/helpers/chem.js +594 -0
  64. package/dist/helpers/compass.js +91 -0
  65. package/dist/helpers/converter.js +74 -0
  66. package/dist/helpers/extractParams.js +77 -0
  67. package/dist/helpers/extractPeaksEdit.js +69 -0
  68. package/dist/helpers/focus.js +15 -0
  69. package/dist/helpers/format.js +403 -0
  70. package/dist/helpers/init.js +80 -0
  71. package/dist/helpers/integration.js +30 -0
  72. package/dist/helpers/mount.js +112 -0
  73. package/dist/helpers/multiplicity.js +44 -0
  74. package/dist/helpers/multiplicity_calc.js +117 -0
  75. package/dist/helpers/multiplicity_complat.js +126 -0
  76. package/dist/helpers/multiplicity_manual.js +94 -0
  77. package/dist/helpers/multiplicity_verify_basic.js +196 -0
  78. package/dist/helpers/shift.js +48 -0
  79. package/dist/helpers/zoom.js +32 -0
  80. package/dist/index.js +705 -0
  81. package/dist/layer_content.js +125 -0
  82. package/dist/layer_init.js +231 -0
  83. package/dist/layer_prism.js +186 -0
  84. package/dist/reducers/index.js +89 -0
  85. package/dist/reducers/reducer_edit_peak.js +111 -0
  86. package/dist/reducers/reducer_forecast.js +113 -0
  87. package/dist/reducers/reducer_integration.js +136 -0
  88. package/dist/reducers/reducer_jcamp.js +74 -0
  89. package/dist/reducers/reducer_layout.js +27 -0
  90. package/dist/reducers/reducer_manager.js +20 -0
  91. package/dist/reducers/reducer_meta.js +30 -0
  92. package/dist/reducers/reducer_multiplicity.js +131 -0
  93. package/dist/reducers/reducer_scan.js +55 -0
  94. package/dist/reducers/reducer_shift.js +99 -0
  95. package/dist/reducers/reducer_simulation.js +30 -0
  96. package/dist/reducers/reducer_status.js +41 -0
  97. package/dist/reducers/reducer_submit.js +54 -0
  98. package/dist/reducers/reducer_threshold.js +34 -0
  99. package/dist/reducers/reducer_ui.js +46 -0
  100. package/dist/reducers/undo_redo_config.js +24 -0
  101. package/dist/sagas/index.js +50 -0
  102. package/dist/sagas/saga_edit_peak.js +84 -0
  103. package/dist/sagas/saga_manager.js +116 -0
  104. package/dist/sagas/saga_meta.js +46 -0
  105. package/dist/sagas/saga_multiplicity.js +387 -0
  106. package/dist/sagas/saga_ui.js +392 -0
  107. package/dist/third_party/jAnalyzer.js +596 -0
  108. package/dist/third_party/peakInterval.js +107 -0
  109. 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;