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,330 @@
1
+ __author__ = "Yuri E. Corilo"
2
+ __date__ = "Jul 31, 2019"
3
+
4
+ from copy import deepcopy
5
+ from threading import Thread
6
+
7
+ from numpy import average, std
8
+
9
+ from corems.molecular_id.calc.ClusterFilter import ClusteringFilter
10
+ from corems.molecular_id.factory.molecularSQL import MolForm_SQL
11
+ from corems.molecular_id.search.molecularFormulaSearch import SearchMolecularFormulas
12
+
13
+
14
+ class FindOxygenPeaks(Thread):
15
+ """Class to find Oxygen peaks in a mass spectrum for formula assignment search
16
+
17
+ Class to walk 14Da units over oxygen space for negative ion mass spectrum of natural organic matter
18
+ Returns a list of MSPeak class containing the possible Molecular Formula class objects.
19
+
20
+ Parameters
21
+ ----------
22
+ mass_spectrum_obj : MassSpec class
23
+ This is where we store MassSpec class obj,
24
+
25
+ lookupTableSettings: MolecularLookupTableSettings class
26
+ This is where we store MolecularLookupTableSettings class obj
27
+
28
+ min_O , max_O : int
29
+ minium and maximum of Oxygen to allow the software to look for
30
+ it will override the settings at lookupTableSettings.usedAtoms
31
+ default min = 1, max = 22
32
+
33
+ Attributes
34
+ ----------
35
+ mass_spectrum_obj : MassSpec class
36
+ This is where we store MassSpec class obj,
37
+ lookupTableSettings: MolecularLookupTableSettings class
38
+ This is where we store MolecularLookupTableSettings class obj
39
+
40
+ Methods
41
+ ----------
42
+ * run().
43
+ will be called when the instantiated class method start is called
44
+ * get_list_found_peaks().
45
+ returns a list of MSpeaks classes cotaining all the MolecularFormula candidates inside the MSPeak
46
+ for more details of the structure see MSPeak class and MolecularFormula class
47
+ * set_mass_spec_indexes_by_found_peaks().
48
+ set the mass spectrum to interate over only the selected indexes
49
+ """
50
+
51
+ def __init__(
52
+ self, mass_spectrum_obj, sql_db: bool = False, min_O: int = 1, max_O: int = 22
53
+ ):
54
+ Thread.__init__(self)
55
+
56
+ self.mass_spectrum_obj = mass_spectrum_obj
57
+ self.min_0 = min_O
58
+ self.max_O = max_O
59
+
60
+ if not sql_db:
61
+ self.sql_db = MolForm_SQL(
62
+ mass_spectrum_obj.molecular_search_settings.url_database
63
+ )
64
+ else:
65
+ self.sql_db = sql_db
66
+
67
+ def run(self):
68
+ """Run the thread"""
69
+ # save initial settings min peaks per class filter
70
+ initial_min_peak_bool = deepcopy(
71
+ self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter
72
+ )
73
+
74
+ # deactivate the usage of min peaks per class filter
75
+ self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False
76
+
77
+ # save initial settings for Ox
78
+ initial_ox = deepcopy(
79
+ self.mass_spectrum_obj.molecular_search_settings.usedAtoms["O"]
80
+ )
81
+
82
+ # resets the used atoms to look only for oxygen organic compounds
83
+ self.mass_spectrum_obj.molecular_search_settings.usedAtoms["O"] = (
84
+ self.min_0,
85
+ self.max_O,
86
+ )
87
+
88
+ self.list_found_mspeaks = []
89
+
90
+ kmd_base = self.mass_spectrum_obj.mspeaks_settings.kendrick_base
91
+
92
+ self.mass_spectrum_obj.change_kendrick_base_all_mspeaks(kmd_base)
93
+
94
+ # needs to be wrapped inside the mass_spec class
95
+ ClusteringFilter().filter_kendrick(self.mass_spectrum_obj)
96
+
97
+ if self.mass_spectrum_obj.parameters.mass_spectrum.verbose_processing:
98
+ print("Start most abundant mass spectral peak search")
99
+ molecular_formula_obj_reference = self.find_most_abundant_formula(
100
+ self.mass_spectrum_obj
101
+ )
102
+
103
+ if self.mass_spectrum_obj.parameters.mass_spectrum.verbose_processing:
104
+ print(
105
+ "Select most abundant peak with molecular formula = %s with a m/z error of %s ppm"
106
+ % (
107
+ molecular_formula_obj_reference.string,
108
+ molecular_formula_obj_reference.mz_error,
109
+ )
110
+ )
111
+ print("Started mass spectral peak series search")
112
+
113
+ self.list_found_mspeaks = self.find_series_mspeaks(
114
+ self.mass_spectrum_obj, molecular_formula_obj_reference, deltamz=14
115
+ )
116
+
117
+ # reset indexes after done with operation that includes a filter (i.e. ClusteringFilter().filter_kendrick())
118
+
119
+ self.mass_spectrum_obj.molecular_search_settings.usedAtoms["O"] = initial_ox
120
+
121
+ self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = (
122
+ initial_min_peak_bool
123
+ )
124
+
125
+ self.mass_spectrum_obj.reset_indexes()
126
+
127
+ self.mass_spectrum_obj.filter_by_noise_threshold()
128
+ if self.mass_spectrum_obj.parameters.mass_spectrum.verbose_processing:
129
+ print("Done with mass spectral peak series search")
130
+
131
+ self.sql_db.close()
132
+
133
+ def find_most_abundant_formula(self, mass_spectrum_obj):
134
+ """Find the most abundant formula in the mass spectrum
135
+
136
+ Parameters
137
+ ----------
138
+ mass_spectrum_obj : MassSpec class
139
+ Mass spectrum object
140
+
141
+ Returns
142
+ ----------
143
+ MolecularFormula class obj
144
+ most abundant MolecularFormula with the lowest mass error
145
+ """
146
+ # need to find a better way to cut off outliners
147
+ # import matplotlib.pyplot as plt
148
+ # plt.hist(mass_spectrum_obj.abundance, bins=100)
149
+ # plt.show()
150
+
151
+ abundances = mass_spectrum_obj.abundance
152
+ abun_mean = average(abundances, axis=0)
153
+ abun_std = std(abundances, axis=0)
154
+
155
+ upper_limit = abun_mean + 7 * abun_std
156
+ if mass_spectrum_obj.parameters.mass_spectrum.verbose_processing:
157
+ print(
158
+ "Maximum abundance limit = %s and max abundance kendrick cluster = %s"
159
+ % (
160
+ upper_limit,
161
+ max(mass_spectrum_obj, key=lambda m: m.abundance).abundance,
162
+ )
163
+ )
164
+
165
+ mspeak_most_abundant = max(
166
+ mass_spectrum_obj,
167
+ key=lambda m: m.abundance if m.abundance <= upper_limit else 0,
168
+ )
169
+
170
+ print("Searching molecular formulas")
171
+
172
+ SearchMolecularFormulas(mass_spectrum_obj, self.sql_db).run_worker_ms_peaks(
173
+ [mspeak_most_abundant]
174
+ )
175
+
176
+ print("Finished searching molecular formulas")
177
+
178
+ if mspeak_most_abundant:
179
+ return mspeak_most_abundant.best_molecular_formula_candidate
180
+
181
+ else:
182
+ raise Exception(
183
+ "Could not find a possible molecular formula match for the most abundant peak of m/z %.5f"
184
+ % mspeak_most_abundant.mz_exp
185
+ )
186
+
187
+ # return the first option
188
+ # return mspeak_most_abundant[0]
189
+
190
+ def find_most_abundant_formula_test(self, mass_spectrum_obj, settings):
191
+ """[Test function] Find the most abundant formula in the mass spectrum
192
+
193
+ Parameters
194
+ ----------
195
+ mass_spectrum_obj : MassSpec class
196
+ Mass spectrum object
197
+ settings : MolecularSearchSettings class
198
+ Molecular search settings object
199
+
200
+ Returns
201
+ ----------
202
+ MolecularFormula class obj
203
+ most abundant MolecularFormula with the lowest mass error
204
+
205
+ """
206
+ # this function is intended for test only.
207
+ # Have to sort by Kendrick to be able to select the most abundant series
208
+ # then select the most abundant peak inside the series
209
+ # or have the user select the reference mspeak on the gui
210
+
211
+ mspeak_most_abundant = mass_spectrum_obj.most_abundant_mspeak
212
+
213
+ SearchMolecularFormulas(mass_spectrum_obj, self.sql_db).run_worker_ms_peaks(
214
+ [mspeak_most_abundant]
215
+ )
216
+
217
+ if mspeak_most_abundant:
218
+ return mspeak_most_abundant.best_molecular_formula_candidate
219
+
220
+ else:
221
+ raise Exception(
222
+ "Could not find a possible molecular formula match for the most abundant peak of m/z %.5f"
223
+ % mspeak_most_abundant.mz_exp
224
+ )
225
+ # return the first option
226
+ # return mspeak_most_abundant[0]
227
+
228
+ def find_series_mspeaks(
229
+ self, mass_spectrum_obj, molecular_formula_obj_reference, deltamz=14
230
+ ):
231
+ """Find a series of abundant peaks in the mass spectrum for a given molecular formula
232
+
233
+ Parameters
234
+ ----------
235
+ mass_spectrum_obj : MassSpec class
236
+ Mass spectrum object
237
+ molecular_formula_obj_reference : MolecularFormula class
238
+ Molecular formula object
239
+ deltamz : float
240
+ delta m/z to look for peaks
241
+
242
+ Returns
243
+ ----------
244
+ list
245
+ list of MSpeak class objects
246
+ """
247
+ abundances = mass_spectrum_obj.abundance
248
+ abun_mean = average(abundances, axis=0)
249
+ abun_std = std(abundances, axis=0)
250
+ upper_limit = abun_mean + 7 * abun_std
251
+
252
+ list_most_abundant_peaks = list()
253
+
254
+ min_mz = mass_spectrum_obj.min_mz_exp
255
+
256
+ max_mz = mass_spectrum_obj.max_mz_exp
257
+
258
+ initial_nominal_mass = molecular_formula_obj_reference.mz_nominal_calc
259
+
260
+ mass = initial_nominal_mass
261
+
262
+ nominal_masses = []
263
+ while mass <= max_mz:
264
+ # print "shit 1", mass, min_mz
265
+ mass += deltamz
266
+ nominal_masses.append(mass)
267
+
268
+ mass = initial_nominal_mass
269
+ while mass >= min_mz:
270
+ # print "shit 1", mass, min_mz
271
+ mass -= deltamz
272
+ nominal_masses.append(mass)
273
+
274
+ nominal_masses = sorted(nominal_masses)
275
+
276
+ for nominal_mass in nominal_masses:
277
+ first_index, last_index = (
278
+ mass_spectrum_obj.get_nominal_mz_first_last_indexes(nominal_mass)
279
+ )
280
+
281
+ ms_peaks = mass_spectrum_obj[first_index:last_index]
282
+
283
+ if ms_peaks:
284
+ #
285
+ # print (nominal_mass, first_index,
286
+ # last_index,
287
+ # mass_spectrum_obj[first_index].mz_exp,
288
+ # mass_spectrum_obj[last_index].mz_exp
289
+ # )
290
+ #
291
+
292
+ mspeak_most_abundant = max(
293
+ ms_peaks,
294
+ key=lambda m: m.abundance if m.abundance <= upper_limit else 0,
295
+ )
296
+
297
+ # mspeak_most_abundant = max(ms_peaks, key=lambda m: m.abundance)
298
+
299
+ list_most_abundant_peaks.append(mspeak_most_abundant)
300
+ if mass_spectrum_obj.parameters.mass_spectrum.verbose_processing:
301
+ print("Start molecular formula search")
302
+ SearchMolecularFormulas(mass_spectrum_obj, self.sql_db).run_worker_ms_peaks(
303
+ list_most_abundant_peaks
304
+ )
305
+ if mass_spectrum_obj.parameters.mass_spectrum.verbose_processing:
306
+ print("Done molecular formula search")
307
+ return [mspeak for mspeak in list_most_abundant_peaks if mspeak]
308
+
309
+ def get_list_found_peaks(self):
310
+ """Get the list of found peaks
311
+
312
+ Returns
313
+ ----------
314
+ list
315
+ list of MSpeak class objects
316
+ """
317
+ return sorted(self.list_found_mspeaks, key=lambda mp: mp.mz_exp)
318
+
319
+ def set_mass_spec_indexes_by_found_peaks(self):
320
+ """Set the mass spectrum to interate over only the selected indexes.
321
+
322
+ Notes
323
+ ----------
324
+ Warning!!!!
325
+ set the mass spectrum to interate over only the selected indexes
326
+ don not forget to call mass_spectrum_obj.reset_indexes after the job is done
327
+ """
328
+
329
+ indexes = [msp.index for msp in self.list_found_mspeaks]
330
+ self.mass_spectrum_obj.set_indexes(indexes)
@@ -0,0 +1,348 @@
1
+ import re
2
+
3
+ import numpy as np
4
+
5
+ from corems.molecular_id.factory.spectrum_search_results import SpectrumSearchResults
6
+
7
+
8
+ class LCMSSpectralSearch:
9
+ """
10
+ Methods for searching LCMS spectra.
11
+
12
+ This class is designed to be a mixin class for the :obj:`~corems.mass_spectra.factory.lc_class.LCMSBase` class.
13
+
14
+ """
15
+
16
+ @staticmethod
17
+ def get_more_match_quals(
18
+ query_mz_arr, lib_entry, mz_tol_da=0.1, include_fragment_types=False
19
+ ):
20
+ """
21
+ Return additional match qualities between query and library entry.
22
+
23
+ Parameters
24
+ ----------
25
+ query_mz_arr : np.array
26
+ Array of query spectrum. Shape (N, 2), with m/z in the first column
27
+ and abundance in the second.
28
+ lib_entry : dict
29
+ Library spectrum entry, with 'mz' key containing the spectrum in
30
+ the format (mz, abundance),(mz, abundance), i.e. from MetabRef.
31
+ mz_tol_da : float, optional
32
+ Tolerance in Da for matching peaks (in MS2). Default is 0.1.
33
+ include_fragment_types : bool, optional
34
+ If True, include fragment type comparisons in output.
35
+ Defaults to False.
36
+
37
+ Returns
38
+ -------
39
+ tuple
40
+ Tuple of (query_in_lib, query_in_lib_fract, lib_in_query, lib_in_query_fract, query_frags, lib_frags, lib_precursor_mz).
41
+
42
+ Notes
43
+ -----
44
+ query_in_lib : int
45
+ Number of peaks in query that are present in the library entry (within mz_tol_da).
46
+ query_in_lib_fract : float
47
+ Fraction of peaks in query that are present in the library entry (within mz_tol_da).
48
+ lib_in_query : int
49
+ Number of peaks in the library entry that are present in the query (within mz_tol_da).
50
+ lib_in_query_fract : float
51
+ Fraction of peaks in the library entry that are present in the query (within mz_tol_da).
52
+ query_frags : list
53
+ List of unique fragment types present in the query, generally 'MLF' or 'LSF' or both.
54
+ lib_frags : list
55
+ List of unique fragment types present in the library entry, generally 'MLF' or 'LSF' or both.
56
+
57
+ Raises
58
+ ------
59
+ ValueError
60
+ If library entry does not have 'fragment_types' key and include_fragment_types is True.
61
+
62
+ """
63
+
64
+ if "mz" in lib_entry.keys():
65
+ # Get the original mz values from the library entry
66
+ lib_mzs = np.array(
67
+ re.findall(r"\(([^,]+),([^)]+)\)", lib_entry["mz"]), dtype=float
68
+ ).reshape(-1, 2)[:, 0]
69
+ elif "peaks" in lib_entry.keys() and lib_entry["peaks"] is not None:
70
+ lib_mzs = lib_entry["peaks"][:, 0]
71
+
72
+ # Get count and fraction of peaks in query that are in lib entry
73
+ query_in_lib = 0
74
+ for peak in query_mz_arr:
75
+ if np.any(np.isclose(lib_mzs, peak, atol=mz_tol_da)):
76
+ query_in_lib += 1
77
+ query_in_lib_fract = query_in_lib / len(query_mz_arr)
78
+
79
+ # Get count and fraction of peaks in lib that are in query
80
+ lib_in_query = 0
81
+ for peak in lib_mzs:
82
+ if np.any(np.isclose(query_mz_arr, peak, atol=mz_tol_da)):
83
+ lib_in_query += 1
84
+ lib_in_query_fract = lib_in_query / len(lib_mzs)
85
+
86
+ if include_fragment_types:
87
+ # Check that fragment types are present in the library entry
88
+ if "fragment_types" not in lib_entry.keys():
89
+ raise ValueError(
90
+ "Flash entropy library entry must have 'fragment_types' key to include fragment types in output."
91
+ )
92
+
93
+ # Get types of fragments in the lib entry and convert it to a list on ", "
94
+ lib_frags = [x.strip() for x in lib_entry["fragment_types"].split(",")]
95
+ # make list of the fragment types that are present in the query spectrum
96
+ lib_in_query_ids = list(
97
+ set(
98
+ [
99
+ ind
100
+ for ind, x in enumerate(lib_mzs)
101
+ if len(np.where(np.isclose(query_mz_arr, x, atol=mz_tol_da))[0])
102
+ > 0
103
+ ]
104
+ )
105
+ )
106
+ query_frags = list(set([lib_frags[x] for x in lib_in_query_ids]))
107
+ lib_frags = list(set(lib_frags))
108
+
109
+ else:
110
+ query_frags = None
111
+ lib_frags = None
112
+
113
+ return (
114
+ query_in_lib,
115
+ query_in_lib_fract,
116
+ lib_in_query,
117
+ lib_in_query_fract,
118
+ query_frags,
119
+ lib_frags,
120
+ )
121
+
122
+ def fe_search(
123
+ self,
124
+ scan_list,
125
+ fe_lib,
126
+ precursor_mz_list=[],
127
+ use_mass_features=True,
128
+ peak_sep_da=0.01,
129
+ get_additional_metrics=True,
130
+ accumulate_results=False,
131
+ ):
132
+ """
133
+ Search LCMS spectra using a FlashEntropy approach.
134
+
135
+ Parameters
136
+ ----------
137
+ scan_list : list
138
+ List of scan numbers to search.
139
+ fe_lib : :obj:`~ms_entropy.FlashEntropySearch`
140
+ FlashEntropy Search instance.
141
+ precursor_mz_list : list, optional
142
+ List of precursor m/z values to search, by default [], which implies
143
+ matched with mass features; to enable this use_mass_features must be True.
144
+ use_mass_features : bool, optional
145
+ If True, use mass features to get precursor m/z values, by default True.
146
+ If True, will add search results to mass features' ms2_similarity_results attribute.
147
+ peak_sep_da : float, optional
148
+ Minimum separation between m/z peaks spectra in Da. This needs match the
149
+ approximate resolution of the search spectra and the FlashEntropySearch
150
+ instance, by default 0.01.
151
+ get_additional_metrics : bool, optional
152
+ If True, get additional metrics from FlashEntropy search, by default True.
153
+ accumulate_results : bool, optional
154
+ If True, accumulate results with existing spectral_search_results instead of
155
+ replacing them. This allows searching the same scans with multiple libraries
156
+ without overwriting previous results, by default False.
157
+
158
+ Returns
159
+ -------
160
+ None, but adds results to self.spectral_search_results and associates these
161
+ spectral_search_results with mass_features within the self.mass_features dictionary.
162
+
163
+ """
164
+ # Retrieve parameters from self
165
+ # include_fragment_types should used for lipids queries only, not general metabolomics
166
+ include_fragment_types = self.parameters.lc_ms.include_fragment_types
167
+ min_match_score = self.parameters.lc_ms.ms2_min_fe_score
168
+
169
+ # If precursor_mz_list is empty and use_mass_features is True, get precursor m/z values from mass features for each scan in scan_list
170
+ if use_mass_features and len(precursor_mz_list) == 0:
171
+ precursor_mz_list = []
172
+ for scan in scan_list:
173
+ mf_ids = [
174
+ key
175
+ for key, value in self.mass_features.items()
176
+ if scan in value.ms2_mass_spectra
177
+ ]
178
+ precursor_mz = [
179
+ value.mz
180
+ for key, value in self.mass_features.items()
181
+ if key in mf_ids
182
+ ]
183
+ precursor_mz_list.append(precursor_mz)
184
+
185
+ # Check that precursor_mz_list same length as scan_list, if not, raise error
186
+ if len(precursor_mz_list) != len(scan_list):
187
+ raise ValueError("Length of precursor_mz_list is not equal to scan_list.")
188
+
189
+ # Loop through each query spectrum / precursor match and save ids of db spectrum that are decent matches
190
+ overall_results_dict = {}
191
+ for i in np.arange(len(scan_list)):
192
+ scan_oi = scan_list[i]
193
+ if len(self._ms[scan_oi].mspeaks) > 0:
194
+ precursor_mzs = precursor_mz_list[i]
195
+ overall_results_dict[scan_oi] = {}
196
+ for precursor_mz in precursor_mzs:
197
+ query_spectrum = fe_lib.clean_spectrum_for_search(
198
+ precursor_mz=precursor_mz,
199
+ peaks=np.vstack(
200
+ (self._ms[scan_oi].mz_exp, self._ms[scan_oi].abundance)
201
+ ).T,
202
+ precursor_ions_removal_da=None,
203
+ noise_threshold=self._ms[
204
+ scan_oi
205
+ ].parameters.mass_spectrum.noise_threshold_min_relative_abundance
206
+ / 100,
207
+ min_ms2_difference_in_da=peak_sep_da,
208
+ )
209
+ search_results = fe_lib.search(
210
+ precursor_mz=precursor_mz,
211
+ peaks=query_spectrum,
212
+ ms1_tolerance_in_da=self.parameters.mass_spectrum[
213
+ "ms1"
214
+ ].molecular_search.max_ppm_error
215
+ * 10**-6
216
+ * precursor_mz,
217
+ ms2_tolerance_in_da=peak_sep_da * 0.5,
218
+ method={"identity"},
219
+ precursor_ions_removal_da=None,
220
+ noise_threshold=self._ms[
221
+ scan_oi
222
+ ].parameters.mass_spectrum.noise_threshold_min_relative_abundance
223
+ / 100,
224
+ target="cpu",
225
+ )["identity_search"]
226
+ match_inds = np.where(search_results > min_match_score)[0]
227
+
228
+ # If any decent matches are found, add them to the results dictionary
229
+ if len(match_inds) > 0:
230
+ match_scores = search_results[match_inds]
231
+ ref_ms_ids = [fe_lib[x]["id"] for x in match_inds]
232
+ ref_mol_ids = [
233
+ fe_lib[x]["molecular_data_id"] for x in match_inds
234
+ ]
235
+ ref_precursor_mzs = [
236
+ fe_lib[x]["precursor_mz"] for x in match_inds
237
+ ]
238
+ ion_types = [fe_lib[x]["ion_type"] for x in match_inds]
239
+ overall_results_dict[scan_oi][precursor_mz] = {
240
+ "ref_mol_id": ref_mol_ids,
241
+ "ref_ms_id": ref_ms_ids,
242
+ "ref_precursor_mz": ref_precursor_mzs,
243
+ "precursor_mz_error_ppm": [
244
+ (precursor_mz - x) / precursor_mz * 10**6
245
+ for x in ref_precursor_mzs
246
+ ],
247
+ "entropy_similarity": match_scores,
248
+ "ref_ion_type": ion_types,
249
+ }
250
+ # Add database name, if present
251
+ db_name = [
252
+ fe_lib[x].get("database_name") for x in match_inds
253
+ ]
254
+ if db_name is not None:
255
+ overall_results_dict[scan_oi][precursor_mz].update(
256
+ {"database_name": db_name}
257
+ )
258
+ if get_additional_metrics:
259
+ more_match_quals = [
260
+ self.get_more_match_quals(
261
+ self._ms[scan_oi].mz_exp,
262
+ fe_lib[x],
263
+ mz_tol_da=peak_sep_da,
264
+ include_fragment_types=include_fragment_types,
265
+ )
266
+ for x in match_inds
267
+ ]
268
+ overall_results_dict[scan_oi][precursor_mz].update(
269
+ {
270
+ "query_mz_in_ref_n": [
271
+ x[0] for x in more_match_quals
272
+ ],
273
+ "query_mz_in_ref_fract": [
274
+ x[1] for x in more_match_quals
275
+ ],
276
+ "ref_mz_in_query_n": [
277
+ x[2] for x in more_match_quals
278
+ ],
279
+ "ref_mz_in_query_fract": [
280
+ x[3] for x in more_match_quals
281
+ ],
282
+ }
283
+ )
284
+ if include_fragment_types:
285
+ overall_results_dict[scan_oi][precursor_mz].update(
286
+ {
287
+ "query_frag_types": [
288
+ x[4] for x in more_match_quals
289
+ ],
290
+ "ref_frag_types": [
291
+ x[5] for x in more_match_quals
292
+ ],
293
+ }
294
+ )
295
+
296
+ # Drop scans with no results from dictionary
297
+ overall_results_dict = {k: v for k, v in overall_results_dict.items() if v}
298
+
299
+ # Cast each entry as a MS2SearchResults object
300
+ for scan_id in overall_results_dict.keys():
301
+ for precursor_mz in overall_results_dict[scan_id].keys():
302
+ ms2_spectrum = self._ms[scan_id]
303
+ ms2_search_results = overall_results_dict[scan_id][precursor_mz]
304
+ overall_results_dict[scan_id][precursor_mz] = SpectrumSearchResults(
305
+ ms2_spectrum, precursor_mz, ms2_search_results
306
+ )
307
+
308
+ # Add MS2SearchResults to the existing spectral search results dictionary
309
+ if accumulate_results:
310
+ # Merge results with existing spectral_search_results
311
+ for scan_id, precursor_dict in overall_results_dict.items():
312
+ if scan_id in self.spectral_search_results:
313
+ # Scan already has results, merge precursor_mz dictionaries
314
+ self.spectral_search_results[scan_id].update(precursor_dict)
315
+ else:
316
+ # New scan, add entire dictionary
317
+ self.spectral_search_results[scan_id] = precursor_dict
318
+ else:
319
+ # Replace existing results (original behavior)
320
+ self.spectral_search_results.update(overall_results_dict)
321
+
322
+ # If there are mass features, associate the results with each mass feature
323
+ if len(self.mass_features) > 0:
324
+ # Determine which results to associate with mass features
325
+ if accumulate_results:
326
+ # When accumulating, only associate new results from this search
327
+ # to avoid duplicating previously associated results
328
+ results_to_associate = overall_results_dict
329
+ else:
330
+ # When not accumulating, clear existing associations and re-associate all results
331
+ for mass_feature_id in self.mass_features.keys():
332
+ self.mass_features[mass_feature_id].ms2_similarity_results = []
333
+ results_to_associate = self.spectral_search_results
334
+
335
+ for mass_feature_id, mass_feature in self.mass_features.items():
336
+ scan_ids = mass_feature.ms2_scan_numbers
337
+ for ms2_scan_id in scan_ids:
338
+ precursor_mz = mass_feature.mz
339
+ try:
340
+ results_to_associate[ms2_scan_id][precursor_mz]
341
+ except KeyError:
342
+ pass
343
+ else:
344
+ self.mass_features[
345
+ mass_feature_id
346
+ ].ms2_similarity_results.append(
347
+ results_to_associate[ms2_scan_id][precursor_mz]
348
+ )