CoreMS 4.0.0__py3-none-any.whl

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 (171) hide show
  1. corems/__init__.py +63 -0
  2. corems/chroma_peak/__init__.py +0 -0
  3. corems/chroma_peak/calc/ChromaPeakCalc.py +480 -0
  4. corems/chroma_peak/calc/__init__.py +0 -0
  5. corems/chroma_peak/calc/subset.py +196 -0
  6. corems/chroma_peak/factory/__init__.py +0 -0
  7. corems/chroma_peak/factory/chroma_peak_classes.py +1178 -0
  8. corems/encapsulation/__init__.py +0 -0
  9. corems/encapsulation/constant.py +1283 -0
  10. corems/encapsulation/factory/__init__.py +0 -0
  11. corems/encapsulation/factory/parameters.py +392 -0
  12. corems/encapsulation/factory/processingSetting.py +1244 -0
  13. corems/encapsulation/input/__init__.py +0 -0
  14. corems/encapsulation/input/parameter_from_json.py +579 -0
  15. corems/encapsulation/output/__init__.py +0 -0
  16. corems/encapsulation/output/parameter_to_dict.py +142 -0
  17. corems/encapsulation/output/parameter_to_json.py +332 -0
  18. corems/mass_spectra/__init__.py +0 -0
  19. corems/mass_spectra/calc/GC_Calc.py +84 -0
  20. corems/mass_spectra/calc/GC_Deconvolution.py +558 -0
  21. corems/mass_spectra/calc/GC_RI_Calibration.py +44 -0
  22. corems/mass_spectra/calc/MZSearch.py +249 -0
  23. corems/mass_spectra/calc/SignalProcessing.py +656 -0
  24. corems/mass_spectra/calc/__init__.py +0 -0
  25. corems/mass_spectra/calc/lc_calc.py +5790 -0
  26. corems/mass_spectra/calc/lc_calc_operations.py +1127 -0
  27. corems/mass_spectra/factory/GC_Class.py +791 -0
  28. corems/mass_spectra/factory/__init__.py +0 -0
  29. corems/mass_spectra/factory/chromat_data.py +51 -0
  30. corems/mass_spectra/factory/lc_class.py +2721 -0
  31. corems/mass_spectra/input/__init__.py +0 -0
  32. corems/mass_spectra/input/andiNetCDF.py +200 -0
  33. corems/mass_spectra/input/boosterHDF5.py +216 -0
  34. corems/mass_spectra/input/brukerSolarix.py +153 -0
  35. corems/mass_spectra/input/brukerSolarix_utils.py +73 -0
  36. corems/mass_spectra/input/corems_hdf5.py +1710 -0
  37. corems/mass_spectra/input/massList.py +133 -0
  38. corems/mass_spectra/input/mzml.py +668 -0
  39. corems/mass_spectra/input/parserbase.py +239 -0
  40. corems/mass_spectra/input/rawFileReader.py +1839 -0
  41. corems/mass_spectra/output/__init__.py +0 -0
  42. corems/mass_spectra/output/export.py +2800 -0
  43. corems/mass_spectrum/__init__.py +0 -0
  44. corems/mass_spectrum/calc/AutoRecalibration.py +237 -0
  45. corems/mass_spectrum/calc/Calibration.py +602 -0
  46. corems/mass_spectrum/calc/CalibrationCalc.py +253 -0
  47. corems/mass_spectrum/calc/KendrickGroup.py +239 -0
  48. corems/mass_spectrum/calc/MassErrorPrediction.py +436 -0
  49. corems/mass_spectrum/calc/MassSpectrumCalc.py +303 -0
  50. corems/mass_spectrum/calc/MeanResolvingPowerFilter.py +212 -0
  51. corems/mass_spectrum/calc/NoiseCalc.py +371 -0
  52. corems/mass_spectrum/calc/NoiseCalc_Bayes.py +93 -0
  53. corems/mass_spectrum/calc/PeakPicking.py +994 -0
  54. corems/mass_spectrum/calc/__init__.py +0 -0
  55. corems/mass_spectrum/factory/MassSpectrumClasses.py +1753 -0
  56. corems/mass_spectrum/factory/__init__.py +0 -0
  57. corems/mass_spectrum/input/__init__.py +0 -0
  58. corems/mass_spectrum/input/baseClass.py +531 -0
  59. corems/mass_spectrum/input/boosterHDF5.py +161 -0
  60. corems/mass_spectrum/input/coremsHDF5.py +475 -0
  61. corems/mass_spectrum/input/massList.py +402 -0
  62. corems/mass_spectrum/input/numpyArray.py +133 -0
  63. corems/mass_spectrum/output/__init__.py +0 -0
  64. corems/mass_spectrum/output/export.py +841 -0
  65. corems/molecular_formula/__init__.py +0 -0
  66. corems/molecular_formula/calc/MolecularFormulaCalc.py +885 -0
  67. corems/molecular_formula/calc/__init__.py +0 -0
  68. corems/molecular_formula/factory/MolecularFormulaFactory.py +895 -0
  69. corems/molecular_formula/factory/__init__.py +0 -0
  70. corems/molecular_formula/input/__init__.py +0 -0
  71. corems/molecular_formula/input/masslist_ref.py +355 -0
  72. corems/molecular_id/__init__.py +0 -0
  73. corems/molecular_id/calc/ClusterFilter.py +251 -0
  74. corems/molecular_id/calc/MolecularFilter.py +122 -0
  75. corems/molecular_id/calc/SpectralSimilarity.py +632 -0
  76. corems/molecular_id/calc/__init__.py +0 -0
  77. corems/molecular_id/calc/math_distance.py +1637 -0
  78. corems/molecular_id/data/FAMES_REF.msp +980 -0
  79. corems/molecular_id/data/PNNLMetV20191015.msp +157267 -0
  80. corems/molecular_id/factory/EI_SQL.py +650 -0
  81. corems/molecular_id/factory/MolecularLookupTable.py +914 -0
  82. corems/molecular_id/factory/__init__.py +0 -0
  83. corems/molecular_id/factory/classification.py +884 -0
  84. corems/molecular_id/factory/lipid_molecular_metadata.py +50 -0
  85. corems/molecular_id/factory/molecularSQL.py +827 -0
  86. corems/molecular_id/factory/spectrum_search_results.py +119 -0
  87. corems/molecular_id/input/__init__.py +0 -0
  88. corems/molecular_id/input/nistMSI.py +148 -0
  89. corems/molecular_id/search/__init__.py +0 -0
  90. corems/molecular_id/search/compoundSearch.py +214 -0
  91. corems/molecular_id/search/database_interfaces.py +1527 -0
  92. corems/molecular_id/search/findOxygenPeaks.py +330 -0
  93. corems/molecular_id/search/lcms_spectral_search.py +348 -0
  94. corems/molecular_id/search/molecularFormulaSearch.py +1117 -0
  95. corems/molecular_id/search/priorityAssignment.py +723 -0
  96. corems/ms_peak/__init__.py +0 -0
  97. corems/ms_peak/calc/MSPeakCalc.py +1010 -0
  98. corems/ms_peak/calc/__init__.py +0 -0
  99. corems/ms_peak/factory/MSPeakClasses.py +542 -0
  100. corems/ms_peak/factory/__init__.py +0 -0
  101. corems/transient/__init__.py +0 -0
  102. corems/transient/calc/TransientCalc.py +362 -0
  103. corems/transient/calc/__init__.py +0 -0
  104. corems/transient/factory/TransientClasses.py +457 -0
  105. corems/transient/factory/__init__.py +0 -0
  106. corems/transient/input/__init__.py +0 -0
  107. corems/transient/input/brukerSolarix.py +461 -0
  108. corems/transient/input/midasDatFile.py +172 -0
  109. corems-4.0.0.dist-info/METADATA +475 -0
  110. corems-4.0.0.dist-info/RECORD +171 -0
  111. corems-4.0.0.dist-info/WHEEL +5 -0
  112. corems-4.0.0.dist-info/licenses/LICENSE +22 -0
  113. corems-4.0.0.dist-info/top_level.txt +4 -0
  114. examples/archive/scripts/CoreMS_tutorial.py +94 -0
  115. examples/archive/scripts/DI HR-MS Halogens Bruker.py +196 -0
  116. examples/archive/scripts/DI HR-MS MassList.py +385 -0
  117. examples/archive/scripts/GC-MS MetabRef.py +213 -0
  118. examples/archive/scripts/GC-MS NetCDF.py +217 -0
  119. examples/archive/scripts/HR-MS Thermo Raw 21T.py +136 -0
  120. examples/archive/scripts/LC-ICPMS_metal_peaks.py +297 -0
  121. examples/archive/scripts/LCMS-Thermo.py +460 -0
  122. examples/archive/scripts/LCMS_isotopes.py +283 -0
  123. examples/archive/scripts/MSParams_example.py +21 -0
  124. examples/archive/scripts/Molecular Formula Data Aggreation.py +84 -0
  125. examples/archive/scripts/Single Mz Search.py +69 -0
  126. examples/test_notebooks.py +145 -0
  127. ext_lib/ChemstationMSFileReader.dll +0 -0
  128. ext_lib/ChemstationMSFileReader.xml +126 -0
  129. ext_lib/RawFileReaderLicense.doc +0 -0
  130. ext_lib/ThermoFisher.CommonCore.BackgroundSubtraction.dll +0 -0
  131. ext_lib/ThermoFisher.CommonCore.BackgroundSubtraction.xml +2307 -0
  132. ext_lib/ThermoFisher.CommonCore.Data.dll +0 -0
  133. ext_lib/ThermoFisher.CommonCore.Data.xml +28974 -0
  134. ext_lib/ThermoFisher.CommonCore.MassPrecisionEstimator.dll +0 -0
  135. ext_lib/ThermoFisher.CommonCore.MassPrecisionEstimator.xml +241 -0
  136. ext_lib/ThermoFisher.CommonCore.RawFileReader.dll +0 -0
  137. ext_lib/ThermoFisher.CommonCore.RawFileReader.xml +31174 -0
  138. ext_lib/__init__.py +0 -0
  139. ext_lib/dotnet/OpenMcdf.Extensions.dll +0 -0
  140. ext_lib/dotnet/OpenMcdf.dll +0 -0
  141. ext_lib/dotnet/OpenMcdf.xml +1154 -0
  142. ext_lib/dotnet/System.IO.FileSystem.AccessControl.dll +0 -0
  143. ext_lib/dotnet/System.IO.FileSystem.AccessControl.xml +506 -0
  144. ext_lib/dotnet/System.Security.AccessControl.dll +0 -0
  145. ext_lib/dotnet/System.Security.AccessControl.xml +2043 -0
  146. ext_lib/dotnet/System.Security.Principal.Windows.dll +0 -0
  147. ext_lib/dotnet/System.Security.Principal.Windows.xml +1011 -0
  148. ext_lib/dotnet/ThermoFisher.CommonCore.BackgroundSubtraction.dll +0 -0
  149. ext_lib/dotnet/ThermoFisher.CommonCore.BackgroundSubtraction.xml +2307 -0
  150. ext_lib/dotnet/ThermoFisher.CommonCore.Data.dll +0 -0
  151. ext_lib/dotnet/ThermoFisher.CommonCore.Data.xml +29148 -0
  152. ext_lib/dotnet/ThermoFisher.CommonCore.MassPrecisionEstimator.dll +0 -0
  153. ext_lib/dotnet/ThermoFisher.CommonCore.MassPrecisionEstimator.xml +241 -0
  154. ext_lib/dotnet/ThermoFisher.CommonCore.RawFileReader.dll +0 -0
  155. ext_lib/dotnet/ThermoFisher.CommonCore.RawFileReader.xml +31492 -0
  156. ext_lib/version +18 -0
  157. support_code/atom_parsers/AtomsDescription.py +359 -0
  158. support_code/atom_parsers/CreateAtomsDescription.py +56 -0
  159. support_code/nmdc/filefinder.py +90 -0
  160. support_code/nmdc/lipidomics/lipidomics_workflow.py +748 -0
  161. support_code/nmdc/lipidomics/manifest_examples.py +49 -0
  162. support_code/nmdc/metabolomics/gcms_workflow.py +158 -0
  163. support_code/nmdc/metabolomics/lcms_metabolomics_targeted_search.py +59 -0
  164. support_code/nmdc/metabolomics/lcms_metabolomics_workflow.py +248 -0
  165. support_code/nmdc/metabolomics/metabolomics_collection.py +628 -0
  166. support_code/nmdc/metadata/dms_api.py +42 -0
  167. support_code/nmdc/nom/archived_scripts/nmdc_metadata_gen.py +288 -0
  168. support_code/nmdc/nom/archived_scripts/nom_grow_workflow.py +209 -0
  169. support_code/nmdc/nom/nom_workflow.py +312 -0
  170. support_code/windows_only_importers/BrukerCompassXtract.py +180 -0
  171. support_code/windows_only_importers/ThermoMSFileReader.py +405 -0
@@ -0,0 +1,994 @@
1
+ """
2
+ @author: Yuri E. Corilo
3
+ @date: Jun 27, 2019
4
+ """
5
+
6
+ import warnings
7
+ from numpy import (
8
+ hstack,
9
+ inf,
10
+ isnan,
11
+ where,
12
+ array,
13
+ polyfit,
14
+ nan,
15
+ pad,
16
+ zeros,
17
+ searchsorted,
18
+ )
19
+ from corems.encapsulation.constant import Labels
20
+ from corems.mass_spectra.calc import SignalProcessing as sp
21
+
22
+
23
+ class PeakPicking:
24
+ """Class for peak picking.
25
+
26
+ Parameters
27
+ ----------
28
+ None
29
+
30
+ Attributes
31
+ ----------
32
+ None
33
+
34
+ Methods
35
+ -------
36
+ * prepare_peak_picking_data().
37
+ Prepare the mz, abundance, and frequence data for peak picking.
38
+ * cut_mz_domain_peak_picking().
39
+ Cut the m/z domain for peak picking.
40
+ * extrapolate_axes_for_pp(mz=None, abund=None, freq=None).
41
+ Extrapolate the m/z axis and fill the abundance axis with 0s.
42
+ * do_peak_picking().
43
+ Perform peak picking.
44
+ * find_minima(apex_index, abundance, len_abundance, right=True).
45
+ Find the minima of a peak.
46
+ * linear_fit_calc(intes, massa, index_term, index_sign).
47
+ Algebraic solution to a linear fit.
48
+ * calculate_resolving_power(intes, massa, current_index).
49
+ Calculate the resolving power of a peak.
50
+ * cal_minima(mass, abun).
51
+ Calculate the minima of a peak.
52
+ * calc_centroid(mass, abund, freq).
53
+ Calculate the centroid of a peak.
54
+ * get_threshold(intes).
55
+ Get the intensity threshold for peak picking.
56
+ * algebraic_quadratic(list_mass, list_y).
57
+ Find the apex of a peak - algebraically.
58
+ * find_apex_fit_quadratic(mass, abund, freq, current_index).
59
+ Find the apex of a peak.
60
+ * check_prominence(abun, current_index, len_abundance, peak_height_diff).
61
+ Check the prominence of a peak.
62
+ * use_the_max(mass, abund, current_index, len_abundance, peak_height_diff).
63
+ Use the max peak height as the centroid.
64
+ * calc_centroid_legacy(mass, abund, freq).
65
+ Legacy centroid calculation. Deprecated - for deletion.
66
+
67
+ """
68
+
69
+ def prepare_peak_picking_data(self):
70
+ """Prepare the data for peak picking.
71
+
72
+ This function will prepare the m/z, abundance, and frequency data for peak picking according to the settings.
73
+
74
+ Returns
75
+ -------
76
+ mz : ndarray
77
+ The m/z axis.
78
+ abundance : ndarray
79
+ The abundance axis.
80
+ freq : ndarray or None
81
+ The frequency axis, if available.
82
+ """
83
+ # First apply cut_mz_domain_peak_picking
84
+ mz, abundance, freq = self.cut_mz_domain_peak_picking()
85
+
86
+ # Then extrapolate the axes for peak picking
87
+ if self.settings.picking_point_extrapolate > 0:
88
+ mz, abundance, freq = self.extrapolate_axes_for_pp(mz, abundance, freq)
89
+ return mz, abundance, freq
90
+
91
+ def cut_mz_domain_peak_picking(self):
92
+ """
93
+ Cut the m/z domain for peak picking.
94
+
95
+ Simplified function
96
+
97
+ Returns
98
+ -------
99
+ mz_domain_X_low_cutoff : ndarray
100
+ The m/z values within the specified range.
101
+ mz_domain_low_Y_cutoff : ndarray
102
+ The abundance values within the specified range.
103
+ freq_domain_low_Y_cutoff : ndarray or None
104
+ The frequency values within the specified range, if available.
105
+
106
+ """
107
+ max_picking_mz = self.settings.max_picking_mz
108
+ min_picking_mz = self.settings.min_picking_mz
109
+
110
+ # min_start = where(self.mz_exp_profile > min_picking_mz)[0][0]
111
+ # max_final = where(self.mz_exp_profile < max_picking_mz)[-1][-1]
112
+ min_start = searchsorted(a=self.mz_exp_profile, v=min_picking_mz)
113
+ max_final = searchsorted(a=self.mz_exp_profile, v=max_picking_mz)
114
+
115
+ if self.has_frequency:
116
+ if self.freq_exp_profile.any():
117
+ return (
118
+ self.mz_exp_profile[min_start:max_final],
119
+ self.abundance_profile[min_start:max_final],
120
+ self.freq_exp_profile[min_start:max_final],
121
+ )
122
+
123
+ else:
124
+ return (
125
+ self.mz_exp_profile[min_start:max_final],
126
+ self.abundance_profile[min_start:max_final],
127
+ None,
128
+ )
129
+
130
+ def legacy_cut_mz_domain_peak_picking(self):
131
+ """
132
+ Cut the m/z domain for peak picking.
133
+ DEPRECATED
134
+ Returns
135
+ -------
136
+ mz_domain_X_low_cutoff : ndarray
137
+ The m/z values within the specified range.
138
+ mz_domain_low_Y_cutoff : ndarray
139
+ The abundance values within the specified range.
140
+ freq_domain_low_Y_cutoff : ndarray or None
141
+ The frequency values within the specified range, if available.
142
+
143
+ """
144
+ max_picking_mz = self.settings.max_picking_mz
145
+ min_picking_mz = self.settings.min_picking_mz
146
+
147
+ min_final = where(self.mz_exp_profile > min_picking_mz)[-1][-1]
148
+ min_start = where(self.mz_exp_profile > min_picking_mz)[0][0]
149
+
150
+ (
151
+ mz_domain_X_low_cutoff,
152
+ mz_domain_low_Y_cutoff,
153
+ ) = (
154
+ self.mz_exp_profile[min_start:min_final],
155
+ self.abundance_profile[min_start:min_final],
156
+ )
157
+
158
+ max_final = where(self.mz_exp_profile < max_picking_mz)[-1][-1]
159
+ max_start = where(self.mz_exp_profile < max_picking_mz)[0][0]
160
+
161
+ if self.has_frequency:
162
+ if self.freq_exp_profile.any():
163
+ freq_domain_low_Y_cutoff = self.freq_exp_profile[min_start:min_final]
164
+
165
+ return (
166
+ mz_domain_X_low_cutoff[max_start:max_final],
167
+ mz_domain_low_Y_cutoff[max_start:max_final],
168
+ freq_domain_low_Y_cutoff[max_start:max_final],
169
+ )
170
+
171
+ else:
172
+ return (
173
+ mz_domain_X_low_cutoff[max_start:max_final],
174
+ mz_domain_low_Y_cutoff[max_start:max_final],
175
+ None,
176
+ )
177
+
178
+ @staticmethod
179
+ def extrapolate_axis(initial_array, pts):
180
+ """
181
+ This function will extrapolate an input array in both directions by N pts.
182
+
183
+ Parameters
184
+ ----------
185
+ initial_array : ndarray
186
+ The input array.
187
+ pts : int
188
+ The number of points to extrapolate.
189
+
190
+ Returns
191
+ -------
192
+ ndarray
193
+ The extrapolated array.
194
+
195
+ Notes
196
+ --------
197
+ This is a static method.
198
+ """
199
+ initial_array_len = len(initial_array)
200
+ right_delta = initial_array[-1] - initial_array[-2]
201
+ left_delta = initial_array[1] - initial_array[0]
202
+
203
+ # Create an array with extra space for extrapolation
204
+ pad_array = zeros(initial_array_len + 2 * pts)
205
+
206
+ # Copy original array into the middle of the padded array
207
+ pad_array[pts : pts + initial_array_len] = initial_array
208
+
209
+ # Extrapolate the right side
210
+ for pt in range(pts):
211
+ final_value = initial_array[-1]
212
+ value_to_add = right_delta * (pt + 1)
213
+ new_value = final_value + value_to_add
214
+ pad_array[initial_array_len + pts + pt] = new_value
215
+
216
+ # Extrapolate the left side
217
+ for pt in range(pts):
218
+ first_value = initial_array[0]
219
+ value_to_subtract = left_delta * (pt + 1)
220
+ new_value = first_value - value_to_subtract
221
+ pad_array[pts - pt - 1] = new_value
222
+
223
+ return pad_array
224
+
225
+ def extrapolate_axes_for_pp(self, mz=None, abund=None, freq=None):
226
+ """Extrapolate the m/z axis and fill the abundance axis with 0s.
227
+
228
+ Parameters
229
+ ----------
230
+ mz : ndarray or None
231
+ The m/z axis, if available. If None, the experimental m/z axis is used.
232
+ abund : ndarray or None
233
+ The abundance axis, if available. If None, the experimental abundance axis is used.
234
+ freq : ndarray or None
235
+ The frequency axis, if available. If None, the experimental frequency axis is used.
236
+
237
+ Returns
238
+ -------
239
+ mz : ndarray
240
+ The extrapolated m/z axis.
241
+ abund : ndarray
242
+ The abundance axis with 0s filled.
243
+ freq : ndarray or None
244
+ The extrapolated frequency axis, if available.
245
+
246
+ Notes
247
+ --------
248
+ This function will extrapolate the mz axis by the number of datapoints specified in the settings,
249
+ and fill the abundance axis with 0s.
250
+ This should prevent peak picking issues at the spectrum edge.
251
+
252
+ """
253
+ # Check if the input arrays are provided
254
+ if mz is None or abund is None:
255
+ mz, abund = self.mz_exp_profile, self.abundance_profile
256
+ if self.has_frequency:
257
+ freq = self.freq_exp_profile
258
+ else:
259
+ freq = None
260
+ pts = self.settings.picking_point_extrapolate
261
+ if pts == 0:
262
+ return mz, abund, freq
263
+
264
+ mz = self.extrapolate_axis(mz, pts)
265
+ abund = pad(abund, (pts, pts), mode="constant", constant_values=(0, 0))
266
+ if freq is not None:
267
+ freq = self.extrapolate_axis(freq, pts)
268
+ return mz, abund, freq
269
+
270
+ def do_peak_picking(self):
271
+ """Perform peak picking."""
272
+ mz, abundance, freq = self.prepare_peak_picking_data()
273
+
274
+ if (
275
+ self.label == Labels.bruker_frequency
276
+ or self.label == Labels.midas_frequency
277
+ ):
278
+ self.calc_centroid(mz, abundance, freq)
279
+
280
+ elif self.label == Labels.thermo_profile:
281
+ self.calc_centroid(mz, abundance, freq)
282
+
283
+ elif self.label == Labels.bruker_profile:
284
+ self.calc_centroid(mz, abundance, freq)
285
+
286
+ elif self.label == Labels.booster_profile:
287
+ self.calc_centroid(mz, abundance, freq)
288
+
289
+ elif self.label == Labels.simulated_profile:
290
+ self.calc_centroid(mz, abundance, freq)
291
+
292
+ else:
293
+ raise Exception("Unknow mass spectrum type", self.label)
294
+
295
+ def find_minima(self, apex_index, abundance, len_abundance, right=True):
296
+ """Find the minima of a peak.
297
+
298
+ Parameters
299
+ ----------
300
+ apex_index : int
301
+ The index of the peak apex.
302
+ abundance : ndarray
303
+ The abundance values.
304
+ len_abundance : int
305
+ The length of the abundance array.
306
+ right : bool, optional
307
+ Flag indicating whether to search for minima to the right of the apex (default is True).
308
+
309
+ Returns
310
+ -------
311
+ int
312
+ The index of the minima.
313
+
314
+ """
315
+ j = apex_index
316
+
317
+ if right:
318
+ minima = abundance[j] > abundance[j + 1]
319
+ else:
320
+ minima = abundance[j] > abundance[j - 1]
321
+
322
+ while minima:
323
+ if j == 1 or j == len_abundance - 2:
324
+ break
325
+
326
+ if right:
327
+ j += 1
328
+
329
+ minima = abundance[j] >= abundance[j + 1]
330
+
331
+ else:
332
+ j -= 1
333
+ minima = abundance[j] >= abundance[j - 1]
334
+
335
+ if right:
336
+ return j
337
+ else:
338
+ return j
339
+
340
+ @staticmethod
341
+ def linear_fit_calc(intes, massa, index_term, index_sign):
342
+ """
343
+ Algebraic solution to a linear fit - roughly 25-50x faster than numpy polyfit when passing only two vals and doing a 1st order fit
344
+
345
+ Parameters
346
+ ----------
347
+ intes : ndarray
348
+ The intensity values.
349
+ massa : ndarray
350
+ The mass values.
351
+ index_term : int
352
+ The index of the current term.
353
+ index_sign : str
354
+ The index sign
355
+
356
+ Returns
357
+ -------
358
+ ndarray
359
+ The coefficients of the linear fit.
360
+
361
+ Notes
362
+ --------
363
+ This is a static method.
364
+ """
365
+ if index_sign == "+":
366
+ x1, x2 = massa[index_term], massa[index_term + 1]
367
+ y1, y2 = intes[index_term], intes[index_term + 1]
368
+ elif index_sign == "-":
369
+ x1, x2 = massa[index_term], massa[index_term - 1]
370
+ y1, y2 = intes[index_term], intes[index_term - 1]
371
+ else:
372
+ warnings.warn("error in linear fit calc, unknown index sign")
373
+
374
+ # Calculate the slope (m)
375
+ slope = (y2 - y1) / (x2 - x1)
376
+
377
+ # Calculate the intercept (b)
378
+ intercept = y1 - slope * x1
379
+
380
+ # The coefficients array would be [slope, intercept]
381
+ coefficients = array([slope, intercept])
382
+ return coefficients
383
+
384
+ def calculate_resolving_power(self, intes, massa, current_index):
385
+ """Calculate the resolving power of a peak.
386
+
387
+ Parameters
388
+ ----------
389
+ intes : ndarray
390
+ The intensity values.
391
+ massa : ndarray
392
+ The mass values.
393
+ current_index : int
394
+ The index of the current peak.
395
+
396
+ Returns
397
+ -------
398
+ float
399
+ The resolving power of the peak.
400
+
401
+ Notes
402
+ --------
403
+ This is a conservative calculation of resolving power,
404
+ the peak need to be resolved at least at the half-maximum magnitude,
405
+ otherwise, the combined full width at half maximum is used to calculate resolving power.
406
+
407
+ """
408
+
409
+ peak_height = intes[current_index]
410
+ target_peak_height = peak_height / 2
411
+
412
+ peak_height_minus = peak_height
413
+ peak_height_plus = peak_height
414
+
415
+ # There are issues when a peak is at the high or low limit of a spectrum in finding its local minima and maxima
416
+ # This solution will return nan for resolving power when a peak is possibly too close to an edge to avoid the issue
417
+
418
+ if current_index < 5:
419
+ warnings.warn("peak at low spectrum edge, returning no resolving power")
420
+ return nan
421
+ elif abs(current_index - len(intes)) < 5:
422
+ warnings.warn("peak at high spectrum edge, returning no resolving power")
423
+ return nan
424
+ else:
425
+ pass
426
+
427
+ index_minus = current_index
428
+ while peak_height_minus >= target_peak_height:
429
+ index_minus = index_minus - 1
430
+ if index_minus < 0:
431
+ warnings.warn(
432
+ "Res. calc. warning - peak index minus adjacent to spectrum edge \n \
433
+ Zeroing the first 5 data points of abundance. Peaks at spectrum edge may be incorrectly reported \n \
434
+ Perhaps try to increase picking_point_extrapolate (e.g. to 3)"
435
+ )
436
+ # Pad the first 5 data points with zeros and restart the loop
437
+ intes[:5] = 0
438
+ peak_height_minus = target_peak_height
439
+ index_minus = current_index
440
+ else:
441
+ peak_height_minus = intes[index_minus]
442
+
443
+ if self.mspeaks_settings.legacy_centroid_polyfit:
444
+ x = [massa[index_minus], massa[index_minus + 1]]
445
+ y = [intes[index_minus], intes[index_minus + 1]]
446
+ coefficients = polyfit(x, y, 1)
447
+ else:
448
+ coefficients = self.linear_fit_calc(
449
+ intes, massa, index_minus, index_sign="+"
450
+ )
451
+
452
+ a = coefficients[0]
453
+ b = coefficients[1]
454
+ if self.mspeaks_settings.legacy_resolving_power:
455
+ y_intercept = intes[index_minus] + (
456
+ (intes[index_minus + 1] - intes[index_minus]) / 2
457
+ )
458
+ else:
459
+ y_intercept = target_peak_height
460
+ massa1 = (y_intercept - b) / a
461
+
462
+ index_plus = current_index
463
+ while peak_height_plus >= target_peak_height:
464
+ index_plus = index_plus + 1
465
+
466
+ try:
467
+ peak_height_plus = intes[index_plus]
468
+ except IndexError:
469
+ warnings.warn(
470
+ "Res. calc. warning - peak index plus adjacent to spectrum edge \n \
471
+ Zeroing the last 5 data points of abundance. Peaks at spectrum edge may be incorrectly reported\
472
+ Perhaps try to increase picking_point_extrapolate (e.g. to 3)"
473
+ )
474
+ # Pad the first 5 data points with zeros and restart the loop
475
+ intes[-5:] = 0
476
+ peak_height_plus = target_peak_height
477
+ index_plus = current_index
478
+
479
+ if self.mspeaks_settings.legacy_centroid_polyfit:
480
+ x = [massa[index_plus], massa[index_plus - 1]]
481
+ y = [intes[index_plus], intes[index_plus - 1]]
482
+ coefficients = polyfit(x, y, 1)
483
+ else:
484
+ coefficients = self.linear_fit_calc(
485
+ intes, massa, index_plus, index_sign="-"
486
+ )
487
+
488
+ a = coefficients[0]
489
+ b = coefficients[1]
490
+
491
+ if self.mspeaks_settings.legacy_resolving_power:
492
+ y_intercept = intes[index_plus - 1] + (
493
+ (intes[index_plus] - intes[index_plus - 1]) / 2
494
+ )
495
+ else:
496
+ y_intercept = target_peak_height
497
+
498
+ massa2 = (y_intercept - b) / a
499
+
500
+ if massa1 > massa2:
501
+ resolvingpower = massa[current_index] / (massa1 - massa2)
502
+
503
+ else:
504
+ resolvingpower = massa[current_index] / (massa2 - massa1)
505
+
506
+ return resolvingpower
507
+
508
+ def cal_minima(self, mass, abun):
509
+ """Calculate the minima of a peak.
510
+
511
+ Parameters
512
+ ----------
513
+ mass : ndarray
514
+ The mass values.
515
+ abun : ndarray
516
+ The abundance values.
517
+
518
+ Returns
519
+ -------
520
+ ndarray or None
521
+ The mass values at the minima, if found.
522
+
523
+ """
524
+ abun = -abun
525
+
526
+ dy = abun[1:] - abun[:-1]
527
+
528
+ # replaces nan for infinity
529
+ indices_nan = where(isnan(abun))[0]
530
+
531
+ if indices_nan.size:
532
+ abun[indices_nan] = inf
533
+ dy[where(isnan(dy))[0]] = inf
534
+
535
+ indexes = where((hstack((dy, 0)) < 0) & (hstack((0, dy)) > 0))[0]
536
+
537
+ if indexes.size:
538
+ return mass[indexes], abun[indexes]
539
+
540
+ def calc_centroid(self, mass, abund, freq):
541
+ """Calculate the centroid of a peak.
542
+
543
+ Parameters
544
+ ----------
545
+ mass : ndarray
546
+ The mass values.
547
+ abund : ndarray
548
+ The abundance values.
549
+ freq : ndarray or None
550
+ The frequency values, if available.
551
+
552
+ Returns
553
+ -------
554
+ None
555
+
556
+ """
557
+
558
+ max_height = self.mspeaks_settings.peak_height_max_percent
559
+ max_prominence = self.mspeaks_settings.peak_max_prominence_percent
560
+ min_peak_datapoints = self.mspeaks_settings.min_peak_datapoints
561
+ peak_derivative_threshold = self.mspeaks_settings.peak_derivative_threshold
562
+ max_abun = max(abund)
563
+ peak_height_diff = lambda hi, li: ((abund[hi] - abund[li]) / max_abun) * 100
564
+
565
+ domain = mass
566
+ signal = abund
567
+ len_signal = len(signal)
568
+
569
+ signal_threshold, factor = self.get_threshold(abund)
570
+ max_signal = factor
571
+
572
+ correct_baseline = False
573
+
574
+ include_indexes = sp.peak_picking_first_derivative(
575
+ domain,
576
+ signal,
577
+ max_height,
578
+ max_prominence,
579
+ max_signal,
580
+ min_peak_datapoints,
581
+ peak_derivative_threshold,
582
+ signal_threshold=signal_threshold,
583
+ correct_baseline=correct_baseline,
584
+ abun_norm=1,
585
+ plot_res=False,
586
+ )
587
+
588
+ for indexes_tuple in include_indexes:
589
+ apex_index = indexes_tuple[1]
590
+
591
+ peak_indexes = self.check_prominence(
592
+ abund, apex_index, len_signal, peak_height_diff
593
+ )
594
+
595
+ if peak_indexes:
596
+ mz_exp_centroid, freq_centr, intes_centr = self.find_apex_fit_quadratic(
597
+ mass, abund, freq, apex_index
598
+ )
599
+
600
+ if mz_exp_centroid:
601
+ peak_resolving_power = self.calculate_resolving_power(
602
+ abund, mass, apex_index
603
+ )
604
+ s2n = intes_centr / self.baseline_noise_std
605
+ self.add_mspeak(
606
+ self.polarity,
607
+ mz_exp_centroid,
608
+ abund[apex_index],
609
+ peak_resolving_power,
610
+ s2n,
611
+ indexes_tuple,
612
+ exp_freq=freq_centr,
613
+ ms_parent=self,
614
+ )
615
+ # pyplot.plot(domain[start_index: final_index + 1], signal[start_index:final_index + 1], c='black')
616
+ # pyplot.show()
617
+
618
+ def get_threshold(self, intes):
619
+ """Get the intensity threshold for peak picking.
620
+
621
+ Parameters
622
+ ----------
623
+ intes : ndarray
624
+ The intensity values.
625
+
626
+ Returns
627
+ -------
628
+ float
629
+ The intensity threshold.
630
+ float
631
+ The factor to multiply the intensity threshold by.
632
+ """
633
+
634
+ intes = array(intes).astype(float)
635
+
636
+ noise_threshold_method = self.settings.noise_threshold_method
637
+
638
+ if noise_threshold_method == "minima":
639
+ if self.is_centroid:
640
+ warnings.warn(
641
+ "Auto threshould is disabled for centroid data, returning 0"
642
+ )
643
+ factor = 1
644
+ abundance_threshold = 1e-20
645
+ # print(self.settings.noise_threshold_min_std)
646
+ else:
647
+ abundance_threshold = self.baseline_noise + (
648
+ self.settings.noise_threshold_min_std * self.baseline_noise_std
649
+ )
650
+ factor = 1
651
+
652
+ elif noise_threshold_method == "signal_noise":
653
+ abundance_threshold = self.settings.noise_threshold_min_s2n
654
+ if self.is_centroid:
655
+ factor = 1
656
+ else:
657
+ factor = self.baseline_noise_std
658
+
659
+ elif noise_threshold_method == "relative_abundance":
660
+ abundance_threshold = self.settings.noise_threshold_min_relative_abundance
661
+ factor = intes.max() / 100
662
+
663
+ elif noise_threshold_method == "absolute_abundance":
664
+ abundance_threshold = self.settings.noise_threshold_absolute_abundance
665
+ factor = 1
666
+
667
+ elif noise_threshold_method == "log":
668
+ if self.is_centroid:
669
+ raise Exception("log noise Not tested for centroid data")
670
+ abundance_threshold = self.settings.noise_threshold_log_nsigma
671
+ factor = self.baseline_noise_std
672
+
673
+ else:
674
+ raise Exception(
675
+ "%s method was not implemented, please refer to corems.mass_spectrum.calc.NoiseCalc Class"
676
+ % noise_threshold_method
677
+ )
678
+
679
+ return abundance_threshold, factor
680
+
681
+ @staticmethod
682
+ def algebraic_quadratic(list_mass, list_y):
683
+ """
684
+ Find the apex of a peak - algebraically.
685
+ Faster than using numpy polyfit by ~28x per fit.
686
+
687
+ Parameters
688
+ ----------
689
+ list_mass : ndarray
690
+ list of m/z values (3 points)
691
+ list_y : ndarray
692
+ list of abundance values (3 points)
693
+
694
+ Returns
695
+ -------
696
+ a, b, c: float
697
+ coefficients of the quadratic equation.
698
+
699
+ Notes
700
+ --------
701
+ This is a static method.
702
+ """
703
+ x_1, x_2, x_3 = list_mass
704
+ y_1, y_2, y_3 = list_y
705
+
706
+ a = (
707
+ y_1 / ((x_1 - x_2) * (x_1 - x_3))
708
+ + y_2 / ((x_2 - x_1) * (x_2 - x_3))
709
+ + y_3 / ((x_3 - x_1) * (x_3 - x_2))
710
+ )
711
+
712
+ b = (
713
+ -y_1 * (x_2 + x_3) / ((x_1 - x_2) * (x_1 - x_3))
714
+ - y_2 * (x_1 + x_3) / ((x_2 - x_1) * (x_2 - x_3))
715
+ - y_3 * (x_1 + x_2) / ((x_3 - x_1) * (x_3 - x_2))
716
+ )
717
+
718
+ c = (
719
+ y_1 * x_2 * x_3 / ((x_1 - x_2) * (x_1 - x_3))
720
+ + y_2 * x_1 * x_3 / ((x_2 - x_1) * (x_2 - x_3))
721
+ + y_3 * x_1 * x_2 / ((x_3 - x_1) * (x_3 - x_2))
722
+ )
723
+ return a, b, c
724
+
725
+ def find_apex_fit_quadratic(self, mass, abund, freq, current_index):
726
+ """
727
+ Find the apex of a peak.
728
+
729
+ Parameters
730
+ ----------
731
+ mass : ndarray
732
+ The mass values.
733
+ abund : ndarray
734
+ The abundance values.
735
+ freq : ndarray or None
736
+ The frequency values, if available.
737
+ current_index : int
738
+ The index of the current peak.
739
+
740
+
741
+ Returns
742
+ -------
743
+ float
744
+ The m/z value of the peak apex.
745
+ float
746
+ The frequency value of the peak apex, if available.
747
+ float
748
+ The abundance value of the peak apex.
749
+
750
+ """
751
+ # calc prominence
752
+ # peak_indexes = self.check_prominence(abund, current_index, len_abundance, peak_height_diff )
753
+
754
+ # if not peak_indexes:
755
+
756
+ # return None, None, None, None
757
+
758
+ # else:
759
+
760
+ # fit parabola to three most abundant datapoints
761
+ list_mass = [
762
+ mass[current_index - 1],
763
+ mass[current_index],
764
+ mass[current_index + 1],
765
+ ]
766
+ list_y = [
767
+ abund[current_index - 1],
768
+ abund[current_index],
769
+ abund[current_index + 1],
770
+ ]
771
+
772
+ if self.mspeaks_settings.legacy_centroid_polyfit:
773
+ z = polyfit(list_mass, list_y, 2)
774
+ a = z[0]
775
+ b = z[1]
776
+ else:
777
+ a, b, c = self.algebraic_quadratic(list_mass, list_y)
778
+
779
+ calculated = -b / (2 * a)
780
+
781
+ if calculated < 1 or int(calculated) != int(list_mass[1]):
782
+ mz_exp_centroid = list_mass[1]
783
+
784
+ else:
785
+ mz_exp_centroid = calculated
786
+
787
+ if (
788
+ self.label == Labels.bruker_frequency
789
+ or self.label == Labels.midas_frequency
790
+ ):
791
+ # fit parabola to three most abundant frequency datapoints
792
+ list_freq = [
793
+ freq[current_index - 1],
794
+ freq[current_index],
795
+ freq[current_index + 1],
796
+ ]
797
+ if self.mspeaks_settings.legacy_centroid_polyfit:
798
+ z = polyfit(list_mass, list_y, 2)
799
+ a = z[0]
800
+ b = z[1]
801
+ else:
802
+ a, b, c = self.algebraic_quadratic(list_mass, list_y)
803
+
804
+ calculated_freq = -b / (2 * a)
805
+
806
+ if calculated_freq < 1 or int(calculated_freq) != freq[current_index]:
807
+ freq_centr = list_freq[1]
808
+
809
+ else:
810
+ freq_centr = calculated_freq
811
+
812
+ else:
813
+ freq_centr = None
814
+
815
+ if self.mspeaks_settings.legacy_centroid_polyfit:
816
+ abundance_centroid = abund[current_index]
817
+ else:
818
+ abundance_centroid = a * mz_exp_centroid**2 + b * mz_exp_centroid + c
819
+
820
+ return mz_exp_centroid, freq_centr, abundance_centroid
821
+
822
+ def check_prominence(
823
+ self, abun, current_index, len_abundance, peak_height_diff
824
+ ) -> tuple or False:
825
+ """Check the prominence of a peak.
826
+
827
+ Parameters
828
+ ----------
829
+ abun : ndarray
830
+ The abundance values.
831
+ current_index : int
832
+ The index of the current peak.
833
+ len_abundance : int
834
+ The length of the abundance array.
835
+ peak_height_diff : function
836
+ The function to calculate the peak height difference.
837
+
838
+ Returns
839
+ -------
840
+ tuple or False
841
+ A tuple containing the indexes of the peak, if the prominence is above the threshold.
842
+ Otherwise, False.
843
+
844
+ """
845
+
846
+ final_index = self.find_minima(current_index, abun, len_abundance, right=True)
847
+
848
+ start_index = self.find_minima(current_index, abun, len_abundance, right=False)
849
+
850
+ peak_indexes = (current_index - 1, current_index, current_index + 1)
851
+
852
+ if (
853
+ min(
854
+ peak_height_diff(current_index, start_index),
855
+ peak_height_diff(current_index, final_index),
856
+ )
857
+ > self.mspeaks_settings.peak_min_prominence_percent
858
+ ):
859
+ return peak_indexes
860
+
861
+ else:
862
+ return False
863
+
864
+ def use_the_max(self, mass, abund, current_index, len_abundance, peak_height_diff):
865
+ """Use the max peak height as the centroid
866
+
867
+ Parameters
868
+ ----------
869
+ mass : ndarray
870
+ The mass values.
871
+ abund : ndarray
872
+ The abundance values.
873
+ current_index : int
874
+ The index of the current peak.
875
+ len_abundance : int
876
+ The length of the abundance array.
877
+ peak_height_diff : function
878
+ The function to calculate the peak height difference.
879
+
880
+ Returns
881
+ -------
882
+ float
883
+ The m/z value of the peak apex.
884
+ float
885
+ The abundance value of the peak apex.
886
+ tuple or None
887
+ A tuple containing the indexes of the peak, if the prominence is above the threshold.
888
+ Otherwise, None.
889
+ """
890
+
891
+ peak_indexes = self.check_prominence(
892
+ abund, current_index, len_abundance, peak_height_diff
893
+ )
894
+
895
+ if not peak_indexes:
896
+ return None, None, None
897
+
898
+ else:
899
+ return mass[current_index], abund[current_index], peak_indexes
900
+
901
+ def calc_centroid_legacy(self, mass, abund, freq):
902
+ """Legacy centroid calculation
903
+ Deprecated - for deletion.
904
+
905
+ """
906
+ warnings.warn(
907
+ "Legacy centroid calculation is deprecated. Please use the new centroid calculation method."
908
+ )
909
+ pass
910
+ if False:
911
+ len_abundance = len(abund)
912
+
913
+ max_abundance = max(abund)
914
+
915
+ peak_height_diff = (
916
+ lambda hi, li: ((abund[hi] - abund[li]) / max_abundance) * 100
917
+ )
918
+
919
+ abundance_threshold, factor = self.get_threshold(abund)
920
+ # print(abundance_threshold, factor)
921
+ # find indices of all peaks
922
+ dy = abund[1:] - abund[:-1]
923
+
924
+ # replaces nan for infi nity
925
+ indices_nan = where(isnan(abund))[0]
926
+
927
+ if indices_nan.size:
928
+ abund[indices_nan] = inf
929
+ dy[where(isnan(dy))[0]] = inf
930
+
931
+ indexes = where((hstack((dy, 0)) < 0) & (hstack((0, dy)) > 0))[0]
932
+
933
+ # noise threshold
934
+ if indexes.size and abundance_threshold is not None:
935
+ indexes = indexes[abund[indexes] / factor >= abundance_threshold]
936
+ # filter out 'peaks' within 3 points of the spectrum limits
937
+ # remove entries within 3 points of upper limit
938
+ indexes = [x for x in indexes if (len_abundance - x) > 3]
939
+ # remove entries within 3 points of zero
940
+ indexes = [x for x in indexes if x > 3]
941
+
942
+ for current_index in indexes:
943
+ if self.label == Labels.simulated_profile:
944
+ mz_exp_centroid, intes_centr, peak_indexes = self.use_the_max(
945
+ mass, abund, current_index, len_abundance, peak_height_diff
946
+ )
947
+ if mz_exp_centroid:
948
+ peak_resolving_power = self.calculate_resolving_power(
949
+ abund, mass, current_index
950
+ )
951
+ s2n = intes_centr / self.baseline_noise_std
952
+ freq_centr = None
953
+ self.add_mspeak(
954
+ self.polarity,
955
+ mz_exp_centroid,
956
+ abund[current_index],
957
+ peak_resolving_power,
958
+ s2n,
959
+ peak_indexes,
960
+ exp_freq=freq_centr,
961
+ ms_parent=self,
962
+ )
963
+
964
+ else:
965
+ mz_exp_centroid, freq_centr, intes_centr, peak_indexes = (
966
+ self.find_apex_fit_quadratic(
967
+ mass,
968
+ abund,
969
+ freq,
970
+ current_index,
971
+ len_abundance,
972
+ peak_height_diff,
973
+ )
974
+ )
975
+ if mz_exp_centroid:
976
+ try:
977
+ peak_resolving_power = self.calculate_resolving_power(
978
+ abund, mass, current_index
979
+ )
980
+ except IndexError:
981
+ print("index error, skipping peak")
982
+ continue
983
+
984
+ s2n = intes_centr / self.baseline_noise_std
985
+ self.add_mspeak(
986
+ self.polarity,
987
+ mz_exp_centroid,
988
+ abund[current_index],
989
+ peak_resolving_power,
990
+ s2n,
991
+ peak_indexes,
992
+ exp_freq=freq_centr,
993
+ ms_parent=self,
994
+ )