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,748 @@
1
+ """
2
+ Notes
3
+ --------
4
+ Assumes that ms1 are collected in profile mode, persistent homology not applicable for centroided data.
5
+ """
6
+
7
+ import sys
8
+
9
+ sys.path.append("./")
10
+ from multiprocessing import Pool
11
+ from pathlib import Path
12
+ import datetime
13
+ import toml
14
+ import warnings
15
+
16
+ import pandas as pd
17
+ import time
18
+
19
+ from corems.mass_spectra.input.corems_hdf5 import ReadCoreMSHDFMassSpectra
20
+ from corems.mass_spectra.input.mzml import MZMLSpectraParser
21
+ from corems.mass_spectra.input.rawFileReader import ImportMassSpectraThermoMSFileReader
22
+ from corems.mass_spectra.output.export import LipidomicsExport
23
+ from corems.molecular_id.search.molecularFormulaSearch import (
24
+ SearchMolecularFormulas,
25
+ SearchMolecularFormulasLC,
26
+ )
27
+ from corems.molecular_id.search.database_interfaces import LCLipidLibraryInterface
28
+ from corems.encapsulation.input.parameter_from_json import (
29
+ load_and_set_toml_parameters_lcms,
30
+ )
31
+
32
+
33
+ def instantiate_lcms_obj(file_in):
34
+ """Instantiate a corems LCMS object from a binary file. Pull in ms1 spectra into dataframe (without storing as MassSpectrum objects to save memory)
35
+
36
+ Parameters
37
+ ----------
38
+ file_in : str or Path
39
+ Path to binary file
40
+ verbose : bool
41
+ Whether to print verbose output
42
+
43
+ Returns
44
+ -------
45
+ myLCMSobj : corems LCMS object
46
+ LCMS object with ms1 spectra in dataframe
47
+ """
48
+ # Instantiate parser based on binary file type
49
+ if ".raw" in str(file_in):
50
+ parser = ImportMassSpectraThermoMSFileReader(file_in)
51
+
52
+ if ".mzML" in str(file_in):
53
+ parser = MZMLSpectraParser(file_in)
54
+
55
+ # Instantiate lc-ms data object using parser and pull in ms1 spectra into dataframe (without storing as MassSpectrum objects to save memory)
56
+ myLCMSobj = parser.get_lcms_obj(spectra="ms1")
57
+
58
+ return myLCMSobj
59
+
60
+
61
+ def set_params_on_lcms_obj(myLCMSobj, params_toml, verbose):
62
+ """Set parameters on the LCMS object
63
+
64
+ Parameters
65
+ ----------
66
+ myLCMSobj : corems LCMS object
67
+ LCMS object to set parameters on
68
+ params_toml : str or Path
69
+ Path to toml file with parameters
70
+
71
+ Returns
72
+ -------
73
+ None, sets parameters on the LCMS object
74
+ """
75
+ # Load parameters from toml file
76
+ load_and_set_toml_parameters_lcms(myLCMSobj, params_toml)
77
+
78
+ # If myLCMSobj is a positive mode, remove Cl from atoms used in molecular search
79
+ # This cuts down on the number of molecular formulas searched hugely
80
+ if myLCMSobj.polarity == "positive":
81
+ myLCMSobj.parameters.mass_spectrum["ms1"].molecular_search.usedAtoms.pop("Cl")
82
+ elif myLCMSobj.polarity == "negative":
83
+ myLCMSobj.parameters.mass_spectrum["ms1"].molecular_search.usedAtoms.pop("Na")
84
+
85
+ if verbose:
86
+ print("Parameters set on LCMS object")
87
+
88
+
89
+ def load_scan_translator(scan_translator=None):
90
+ """Translate scans using a scan translator
91
+
92
+ Parameters
93
+ ----------
94
+ scan_translator : str or Path
95
+ Path to scan translator yaml file
96
+
97
+ Returns
98
+ -------
99
+ scan_dict : dict
100
+ Dict with keys as parameter keys and values as lists of scans
101
+ """
102
+ # Convert the scan translator to a dictionary
103
+ if scan_translator is None:
104
+ scan_translator_dict = {"ms2": {"scan_filter": "", "resolution": "high"}}
105
+ else:
106
+ # Convert the scan translator to a dictionary
107
+ if isinstance(scan_translator, str):
108
+ scan_translator = Path(scan_translator)
109
+ # read in the scan translator from toml
110
+ with open(scan_translator, "r") as f:
111
+ scan_translator_dict = toml.load(f)
112
+ for param_key in scan_translator_dict.keys():
113
+ if scan_translator_dict[param_key]["scan_filter"] == "":
114
+ scan_translator_dict[param_key]["scan_filter"] = None
115
+ return scan_translator_dict
116
+
117
+
118
+ def check_scan_translator(myLCMSobj, scan_translator):
119
+ """Check if scan translator is provided and that it maps correctly to scans and parameters"""
120
+ scan_translator_dict = load_scan_translator(scan_translator)
121
+ # Check that the scan translator maps correctly to scans and parameters
122
+ scan_df = myLCMSobj.scan_df
123
+ scans_pulled_out = []
124
+ for param_key in scan_translator_dict.keys():
125
+ assert param_key in myLCMSobj.parameters.mass_spectrum.keys()
126
+ assert "scan_filter" in scan_translator_dict[param_key].keys()
127
+ assert "resolution" in scan_translator_dict[param_key].keys()
128
+ # Pull out scans that match the scan filter
129
+ scan_df_sub = scan_df[
130
+ scan_df.scan_text.str.contains(
131
+ scan_translator_dict[param_key]["scan_filter"]
132
+ )
133
+ ]
134
+ scans_pulled_out.extend(scan_df_sub.scan.tolist())
135
+ if len(scan_df_sub) == 0:
136
+ raise ValueError(
137
+ "No scans pulled out by scan translator for parameter key: ",
138
+ param_key,
139
+ " and scan filter: ",
140
+ scan_translator_dict[param_key]["scan_filter"],
141
+ )
142
+
143
+ # Check that the scans pulled out by the scan translator are not overlapping and assert error if they are
144
+ if len(set(scans_pulled_out)) != len(scans_pulled_out):
145
+ raise ValueError("Overlapping scans pulled out by scan translator")
146
+
147
+
148
+ def add_mass_features(myLCMSobj, scan_translator):
149
+ """Process ms1 spectra and perform molecular search
150
+
151
+ This includes peak picking, adding and processing associated ms1 spectra,
152
+ integration of mass features, annotation of c13 mass features, deconvolution of ms1 mass features,
153
+ and adding of peak shape metrics of mass features to the mass feature dataframe.
154
+
155
+ Parameters
156
+ ----------
157
+ myLCMSobj : corems LCMS object
158
+ LCMS object to process
159
+ scan_translator : str or Path
160
+ Path to scan translator yaml file
161
+
162
+ Returns
163
+ -------
164
+ None, processes the LCMS object
165
+ """
166
+ print("Finding mass features")
167
+ myLCMSobj.find_mass_features()
168
+ print("Found ", len(myLCMSobj.mass_features), " mass features")
169
+
170
+ # Get the scan mode of the ms1 spectra
171
+ ms1_scan_df = myLCMSobj.scan_df[myLCMSobj.scan_df.ms_level == 1]
172
+
173
+
174
+ # Integrate before adding associated ms1 spectra to be able to filter by peak shape metrics?
175
+ print("Integrating mass features")
176
+ myLCMSobj.integrate_mass_features(drop_if_fail=True)
177
+ print("Adding peak shape metrics")
178
+ myLCMSobj.add_peak_metrics()
179
+
180
+
181
+ print("Adding associated ms1 spectra")
182
+ if all(x == "profile" for x in ms1_scan_df.ms_format.to_list()):
183
+ myLCMSobj.add_associated_ms1(
184
+ auto_process=True, use_parser=False, spectrum_mode="profile"
185
+ )
186
+ # If the ms1 data are centroided and the parser if MZMLSpectraParser, set spectrum_mode to centroided but use_parser to False (mzml doesn't have resolving power anyway)
187
+ elif isinstance(myLCMSobj.spectra_parser, MZMLSpectraParser) and all(
188
+ x == "centroid" for x in ms1_scan_df.ms_format.to_list()
189
+ ):
190
+ myLCMSobj.add_associated_ms1(
191
+ auto_process=True, use_parser=False, spectrum_mode="centroid"
192
+ )
193
+ elif all(x == "centroid" for x in ms1_scan_df.ms_format.to_list()):
194
+ myLCMSobj.add_associated_ms1(
195
+ auto_process=True, use_parser=True, spectrum_mode="centroid"
196
+ )
197
+
198
+ # Count and report how many mass features are left after integration
199
+ print("Number of mass features after integration: ", len(myLCMSobj.mass_features))
200
+ #filter_and_plot_mass_features(myLCMSobj)
201
+ """
202
+ print("Annotating C13 mass features")
203
+ myLCMSobj.find_c13_mass_features()
204
+ print("Deconvoluting mass features")
205
+ myLCMSobj.deconvolute_ms1_mass_features()
206
+
207
+ print("Adding associated ms2 spectra")
208
+ scan_dictionary = load_scan_translator(scan_translator=scan_translator)
209
+ for param_key in scan_dictionary.keys():
210
+ scan_filter = scan_dictionary[param_key]["scan_filter"]
211
+ if scan_filter == "":
212
+ scan_filter = None
213
+ myLCMSobj.add_associated_ms2_dda(
214
+ spectrum_mode="centroid", ms_params_key=param_key, scan_filter=scan_filter
215
+ )
216
+ """
217
+
218
+ def filter_and_plot_mass_features(myLCMSobj):
219
+ """Filter mass features based on peak shape metrics and plot composite feature map
220
+
221
+ Helpful for visualizing and optimizing filtering parameters
222
+
223
+ Parameters
224
+ ----------
225
+ myLCMSobj : corems LCMS object
226
+ LCMS object to process
227
+
228
+ Returns
229
+ -------
230
+ None, processes the LCMS object
231
+ """
232
+ # Filter mass features based on peak shape metrics
233
+ my_df_og = myLCMSobj.mass_features_to_df()
234
+ # Make mf_df with only mass features that pass the filters
235
+ mf_df = my_df_og[
236
+ # use normalized_dispersity_index, and set to less than 0.2
237
+ (my_df_og["noise_score_max"] > 0.8)
238
+ & (my_df_og["noise_score_min"] > 0.5)
239
+ ]
240
+ mf_ids_to_keep = mf_df.index.tolist()
241
+
242
+ # Make a mf_df with dropped mass features
243
+ mf_df_dropped = my_df_og[~my_df_og.index.isin(mf_ids_to_keep)]
244
+
245
+ # Plot 50 random "good" mass features
246
+ import numpy as np
247
+ import matplotlib.pyplot as plt
248
+ good_mf_dir = Path("good_mass_features")
249
+ # delete good_mf_dir if it exists
250
+ if good_mf_dir.exists():
251
+ import shutil
252
+ shutil.rmtree(good_mf_dir)
253
+ good_mf_dir.mkdir(exist_ok=True)
254
+ # save df of dropped mass features in good_mf_dir
255
+ mf_df.to_csv(good_mf_dir / "good_mass_features.csv", index=True)
256
+ for mf_id in np.random.choice(mf_ids_to_keep, min(50, len(mf_ids_to_keep)), replace=False):
257
+ fig = myLCMSobj.mass_features[mf_id].plot(return_fig=True, plot_smoothed_eic=True, plot_eic_datapoints=True)
258
+ # Add the noise_score_max and dispersity_index to the subtitle
259
+ fig.suptitle(f"mf_id: {mf_id}, noise_score_max: {myLCMSobj.mass_features[mf_id].noise_score_max:.2f}, dispersity_index: {myLCMSobj.mass_features[mf_id].dispersity_index:.2f}")
260
+ fig.savefig(good_mf_dir / f"mf_{mf_id}_good.png", dpi=300, bbox_inches="tight")
261
+ plt.close(fig)
262
+
263
+ # Do the same for dropped mass features
264
+ dropped_mf_dir = Path("dropped_mass_features")
265
+ # delete dropped_mf_dir if it exists
266
+ if dropped_mf_dir.exists():
267
+ import shutil
268
+ shutil.rmtree(dropped_mf_dir)
269
+ dropped_mf_dir.mkdir(exist_ok=True)
270
+ # save df of dropped mass features in dropped_mf_dir
271
+ mf_df_dropped.to_csv(dropped_mf_dir / "dropped_mass_features.csv", index=True)
272
+ for mf_id in np.random.choice(mf_df_dropped.index.tolist(), min(50, len(mf_df_dropped)), replace=False):
273
+ fig = myLCMSobj.mass_features[mf_id].plot(return_fig=True, plot_smoothed_eic=True, plot_eic_datapoints=True)
274
+ # Add the noise_score_max and dispersity_index to the subtitle
275
+ fig.suptitle(f"mf_id: {mf_id}, noise_score_max: {myLCMSobj.mass_features[mf_id].noise_score_max:.2f}, dispersity_index: {myLCMSobj.mass_features[mf_id].dispersity_index:.2f}")
276
+ fig.savefig(dropped_mf_dir / f"mf_{mf_id}_dropped.png", dpi=300, bbox_inches="tight")
277
+ plt.close(fig)
278
+
279
+ print("Number of mass features after filtering: ", len(mf_ids_to_keep), " out of ", len(my_df_og), " (", round(len(mf_ids_to_keep)/len(my_df_og)*100, 2), "% )")
280
+ print("Mass features filtered out due to dispersity_index >= 0.8 or noise_score_max <= 0.6")
281
+
282
+
283
+
284
+ def molecular_formula_search(myLCMSobj):
285
+ """Perform molecular search on ms1 spectra
286
+
287
+ Parameters
288
+ ----------
289
+ myLCMSobj : corems LCMS object
290
+ LCMS object to process
291
+
292
+ Returns
293
+ -------
294
+ None, processes the LCMS object
295
+ """
296
+ mol_search = SearchMolecularFormulasLC(myLCMSobj)
297
+ mol_search.run_mass_feature_search()
298
+ print("Finished molecular search")
299
+
300
+
301
+ def export_results(myLCMSobj, out_path, molecular_metadata=None, final=False):
302
+ """Export results to hdf5 and csv as a lipid report
303
+
304
+ Parameters
305
+ ----------
306
+ myLCMSobj : corems LCMS object
307
+ LCMS object to process
308
+ out_path : str or Path
309
+ Path to output file
310
+ molecular_metadata : dict
311
+ Dict with molecular metadata
312
+ final : bool
313
+ Whether to export final results
314
+
315
+ Returns
316
+ -------
317
+ None, exports results to hdf5 and csv as a lipid report
318
+ """
319
+ exporter = LipidomicsExport(out_path, myLCMSobj)
320
+ exporter.to_hdf(overwrite=True)
321
+ if final:
322
+ # Do not show warnings, these are expected
323
+ exporter.report_to_csv(molecular_metadata=molecular_metadata)
324
+ else:
325
+ with warnings.catch_warnings():
326
+ warnings.simplefilter("ignore")
327
+ exporter.report_to_csv()
328
+
329
+
330
+ def save_times(myLCMSobj, time_start, out_path, time_end=None):
331
+ """Get times for processing steps
332
+
333
+ Parameters
334
+ ----------
335
+ myLCMSobj : corems LCMS object
336
+ LCMS object to process
337
+ time_start : float
338
+ Start time of processing
339
+ out_path : str or Path
340
+ Path to output file
341
+ time_end : float
342
+ End time of processing
343
+
344
+ Returns
345
+ -------
346
+ None, writes out times to a file within the output directory
347
+ """
348
+ # Check if out_path (with .corems) exisits
349
+ out_dir = Path(str(out_path) + ".corems/")
350
+ if not out_dir.exists():
351
+ print("Output directory does not exist")
352
+
353
+ time_toml_path = out_dir / "times.toml"
354
+ if not time_toml_path.exists():
355
+ raw_data_creation_time = myLCMSobj.spectra_parser.get_creation_time().strftime(
356
+ "%Y-%m-%dT%H:%M:%SZ"
357
+ )
358
+ processed_data_creation_time = time_start.strftime("%Y-%m-%dT%H:%M:%SZ")
359
+ time_dict = {
360
+ "raw_data_creation_time": raw_data_creation_time,
361
+ "metabolomics_workflow_start_time": processed_data_creation_time,
362
+ }
363
+ # save as a toml file
364
+ toml_string = toml.dumps(time_dict) # Output to a string
365
+ with open(time_toml_path, "w") as f:
366
+ f.write(toml_string)
367
+ elif time_toml_path.exists() and time_end is not None:
368
+ time_dict = toml.load(time_toml_path)
369
+ time_dict["metabolomics_workflow_end_time"] = time_end.strftime(
370
+ "%Y-%m-%dT%H:%M:%SZ"
371
+ )
372
+ toml_string = toml.dumps(time_dict)
373
+ with open(time_toml_path, "w") as f:
374
+ f.write(toml_string)
375
+
376
+
377
+ def process_ms2(myLCMSobj, metadata, scan_translator):
378
+ """Process ms2 spectra and perform molecular search
379
+
380
+ Parameters
381
+ ----------
382
+ myLCMSobj : corems LCMS object
383
+ LCMS object to process
384
+ metadata : dict
385
+ Dict with keys "mzs", "fe", and "molecular_metadata" with values of dicts of precursor mzs (negative and positive), flash entropy search databases (negative and positive), and molecular metadata, respectively
386
+
387
+ Returns
388
+ -------
389
+ None, processes the LCMS object
390
+ """
391
+ # Perform molecular search on ms2 spectra
392
+ # Grab fe from metatdata associated with polarity (this is inherently high resolution as its from a in-silico high res library)
393
+ fe_search = metadata["fe"][myLCMSobj.polarity]
394
+
395
+ scan_dictionary = load_scan_translator(scan_translator)
396
+ ms2_scan_df = myLCMSobj.scan_df[myLCMSobj.scan_df.ms_level == 2]
397
+
398
+ # Process high resolution MS2 scans
399
+ # Collect all high resolution MS2 scans using the scan translator
400
+ for param_key in scan_dictionary.keys():
401
+ ms2_scans_oi_hr = []
402
+ if scan_dictionary[param_key]["resolution"] == "high":
403
+ scan_filter = scan_dictionary[param_key]["scan_filter"]
404
+ if scan_filter is not None:
405
+ ms2_scan_df_hr = ms2_scan_df[
406
+ ms2_scan_df.scan_text.str.contains(scan_filter)
407
+ ]
408
+ else:
409
+ ms2_scan_df_hr = ms2_scan_df
410
+ ms2_scans_oi_hr_i = [
411
+ x for x in ms2_scan_df_hr.scan.tolist() if x in myLCMSobj._ms.keys()
412
+ ]
413
+ ms2_scans_oi_hr.extend(ms2_scans_oi_hr_i)
414
+ # Perform search on high res scans
415
+ if len(ms2_scans_oi_hr) > 0:
416
+ myLCMSobj.fe_search(
417
+ scan_list=ms2_scans_oi_hr, fe_lib=fe_search, peak_sep_da=0.01
418
+ )
419
+
420
+ # Process low resolution MS2 scans
421
+ # Collect all low resolution MS2 scans using the scan translator
422
+ for param_key in scan_dictionary.keys():
423
+ ms2_scans_oi_lr = []
424
+ if scan_dictionary[param_key]["resolution"] == "low":
425
+ scan_filter = scan_dictionary[param_key]["scan_filter"]
426
+ if scan_filter is not None:
427
+ ms2_scan_df_lr = ms2_scan_df[
428
+ ms2_scan_df.scan_text.str.contains(scan_filter)
429
+ ]
430
+ else:
431
+ ms2_scan_df_lr = ms2_scan_df
432
+ ms2_scans_oi_lri = [
433
+ x for x in ms2_scan_df_lr.scan.tolist() if x in myLCMSobj._ms.keys()
434
+ ]
435
+ ms2_scans_oi_lr.extend(ms2_scans_oi_lri)
436
+ # Perform search on low res scans
437
+ if len(ms2_scans_oi_lr) > 0:
438
+ # Recast the flashentropy search database to low resolution
439
+ lipid_library = LCLipidLibraryInterface()
440
+ fe_search_lr = lipid_library._to_flashentropy(
441
+ spectral_library=fe_search,
442
+ normalize=True,
443
+ fe_kwargs={
444
+ "normalize_intensity": True,
445
+ "min_ms2_difference_in_da": 0.4,
446
+ "max_ms2_tolerance_in_da": 0.2,
447
+ "max_indexed_mz": 3000,
448
+ "precursor_ions_removal_da": None,
449
+ "noise_threshold": 0,
450
+ },
451
+ )
452
+ myLCMSobj.fe_search(
453
+ scan_list=ms2_scans_oi_lr, fe_lib=fe_search_lr, peak_sep_da=0.3
454
+ )
455
+
456
+
457
+ def run_lipid_sp_ms1(
458
+ file_in,
459
+ out_path,
460
+ params_toml,
461
+ scan_translator=None,
462
+ verbose=True,
463
+ return_mzs=True,
464
+ ms1_molecular_search=True,
465
+ ):
466
+ """Run signal processing, get associated ms1, add associated ms2, do ms1 molecular search, and export intermediate results
467
+
468
+ Parameters
469
+ ----------
470
+ file_in : str or Path
471
+ Path to binary file
472
+ out_path : str or Path
473
+ Path to output file
474
+ params_toml : str or Path
475
+ Path to toml file with parameters
476
+ verbose : bool
477
+ Whether to print verbose output
478
+ return_mzs : bool
479
+ Whether to return precursor mzs
480
+
481
+ Returns
482
+ -------
483
+ mz_dict : dict
484
+ Dict with keys "positive" and "negative" and values of lists of precursor mzs
485
+ """
486
+ time_start = datetime.datetime.now()
487
+ myLCMSobj = instantiate_lcms_obj(file_in)
488
+ set_params_on_lcms_obj(myLCMSobj, params_toml, verbose)
489
+ check_scan_translator(myLCMSobj, scan_translator)
490
+ add_mass_features(myLCMSobj, scan_translator)
491
+ myLCMSobj.remove_unprocessed_data()
492
+ # myLCMSobj.parameters.mass_spectrum['ms1'].molecular_search.verbose_processing = False
493
+ if ms1_molecular_search:
494
+ molecular_formula_search(myLCMSobj)
495
+ export_results(myLCMSobj, out_path=out_path, final=False)
496
+ save_times(myLCMSobj, time_start, out_path)
497
+ if return_mzs:
498
+ precursor_mz_list = list(
499
+ set(
500
+ [
501
+ v.mz
502
+ for k, v in myLCMSobj.mass_features.items()
503
+ if len(v.ms2_scan_numbers) > 0 and v.isotopologue_type is None
504
+ ]
505
+ )
506
+ )
507
+ mz_dict = {myLCMSobj.polarity: precursor_mz_list}
508
+ return mz_dict
509
+
510
+
511
+ def prep_metadata(mz_dicts, out_dir, db_location):
512
+ """Prepare metadata for ms2 spectral search
513
+
514
+ Parameters
515
+ ----------
516
+ mz_dicts : list of dicts
517
+ List of dicts with keys "positive" and "negative" and values of lists of precursor mzs
518
+ out_dir : Path
519
+ Path to output directory
520
+ db_location : str | Path
521
+ Path to local lipid sqlite library.
522
+
523
+ Returns
524
+ -------
525
+ metadata : dict
526
+ Dict with keys "mzs", "fe", and "molecular_metadata" with values of dicts of precursor mzs (negative and positive), flash entropy search databases (negative and positive), and molecular metadata, respectively
527
+
528
+ Notes
529
+ -------
530
+ Also writes out files for the flash entropy search databases and molecular metadata
531
+ """
532
+ metadata = {
533
+ "mzs": {"positive": None, "negative": None},
534
+ "fe": {"positive": None, "negative": None},
535
+ "molecular_metadata": {},
536
+ }
537
+ for d in mz_dicts:
538
+ metadata["mzs"].update(d)
539
+
540
+ lipid_library = LCLipidLibraryInterface(db_location=db_location)
541
+
542
+ print("Preparing positive lipid library")
543
+ if metadata["mzs"]["positive"] is not None:
544
+ positive_library, lipidmetadata_positive = lipid_library.get_lipid_library(
545
+ mz_list=metadata["mzs"]["positive"],
546
+ polarity="positive",
547
+ mz_tol_ppm=5,
548
+ format="flashentropy",
549
+ normalize=True,
550
+ fe_kwargs={
551
+ "normalize_intensity": True,
552
+ "min_ms2_difference_in_da": 0.02, # for cleaning spectra
553
+ "max_ms2_tolerance_in_da": 0.01, # for setting search space
554
+ "max_indexed_mz": 3000,
555
+ "precursor_ions_removal_da": None,
556
+ "noise_threshold": 0,
557
+ },
558
+ )
559
+ metadata["fe"]["positive"] = positive_library
560
+ metadata["molecular_metadata"].update(lipidmetadata_positive)
561
+ fe_positive_df = pd.DataFrame.from_dict(
562
+ {k: v for k, v in enumerate(metadata["fe"]["positive"])}, orient="index"
563
+ )
564
+ fe_positive_df.to_csv(out_dir / "ms2_db_positive.csv")
565
+
566
+ print("Preparing negative lipid library")
567
+ if metadata["mzs"]["negative"] is not None:
568
+ negative_library, lipidmetadata_negative = lipid_library.get_lipid_library(
569
+ mz_list=metadata["mzs"]["negative"],
570
+ polarity="negative",
571
+ mz_tol_ppm=5,
572
+ mz_tol_da_api=0.01,
573
+ format="flashentropy",
574
+ normalize=True,
575
+ fe_kwargs={
576
+ "normalize_intensity": True,
577
+ "min_ms2_difference_in_da": 0.02, # for cleaning spectra
578
+ "max_ms2_tolerance_in_da": 0.01, # for setting search space
579
+ "max_indexed_mz": 3000,
580
+ "precursor_ions_removal_da": None,
581
+ "noise_threshold": 0,
582
+ },
583
+ )
584
+ metadata["fe"]["negative"] = negative_library
585
+ metadata["molecular_metadata"].update(lipidmetadata_negative)
586
+ fe_negative_df = pd.DataFrame.from_dict(
587
+ {k: v for k, v in enumerate(metadata["fe"]["negative"])}, orient="index"
588
+ )
589
+ fe_negative_df.to_csv(out_dir / "ms2_db_negative.csv")
590
+
591
+ mol_metadata_df = pd.concat(
592
+ [
593
+ pd.DataFrame.from_dict(v.__dict__, orient="index").transpose()
594
+ for k, v in metadata["molecular_metadata"].items()
595
+ ],
596
+ ignore_index=True,
597
+ )
598
+ mol_metadata_df.to_csv(out_dir / "molecular_metadata.csv")
599
+ return metadata
600
+
601
+
602
+ def run_lipid_ms2(out_path, metadata, scan_translator=None):
603
+ """Run ms2 spectral search and export final results
604
+
605
+ Parameters
606
+ ----------
607
+ out_path : str or Path
608
+ Path to output file
609
+ metadata : dict
610
+ Dict with keys "mzs", "fe", and "molecular_metadata" with values of dicts of precursor mzs (negative and positive), flash entropy search databases (negative and positive), and molecular metadata, respectively
611
+
612
+ Returns
613
+ -------
614
+ None, runs ms2 spectral search and exports final results
615
+ """
616
+ # Read in the intermediate results
617
+ out_path_hdf5 = str(out_path) + ".corems/" + out_path.stem + ".hdf5"
618
+ parser = ReadCoreMSHDFMassSpectra(out_path_hdf5)
619
+ myLCMSobj = parser.get_lcms_obj()
620
+
621
+ # Process ms2 spectra, perform spectral search, and export final results
622
+ process_ms2(myLCMSobj, metadata, scan_translator=scan_translator)
623
+ export_results(myLCMSobj, str(out_path), metadata["molecular_metadata"], final=True)
624
+ time_end = datetime.datetime.now()
625
+ save_times(myLCMSobj, time_start=None, out_path=out_path, time_end=time_end)
626
+
627
+
628
+ def run_lipid_workflow(
629
+ file_dir,
630
+ out_dir,
631
+ params_toml,
632
+ db_location,
633
+ scan_translator=None,
634
+ verbose=True,
635
+ ms1_molecular_search=False, # whether to do ms1 molecular search
636
+ cores=1,
637
+ ):
638
+ """Run lipidomics workflow
639
+
640
+ Parameters
641
+ ----------
642
+ file_dir : str or Path
643
+ Path to directory with raw or mzml lipid files
644
+ out_dir : str or Path
645
+ Path to output directory
646
+ params_toml : str or Path
647
+ Path to toml file with parameters
648
+ db_location : str or Path
649
+ Path to local lipid sqlite library
650
+ verbose : bool
651
+ Whether to print verbose output
652
+ cores : int
653
+ Number of cores to use
654
+
655
+ Returns
656
+ -------
657
+ None, runs lipidomics workflow and exports final results
658
+ """
659
+ # Make output dir and get list of files to process
660
+ out_dir.mkdir(parents=True, exist_ok=True)
661
+ files_list = list(file_dir.glob("*.raw"))
662
+ out_paths_list = [out_dir / f.stem for f in files_list]
663
+
664
+ # Run signal processing, get associated ms1, add associated ms2, and export temp results
665
+ if cores == 1 or len(files_list) == 1:
666
+ mz_dicts = []
667
+ for file_in, file_out in list(zip(files_list, out_paths_list)):
668
+ # Check if folder already exists
669
+ if Path(str(file_out) + ".corems").exists():
670
+ print("File already exists, skipping: ", file_out)
671
+ continue
672
+ if verbose:
673
+ print("Processing file: ", file_in)
674
+ mz_dict = run_lipid_sp_ms1(
675
+ file_in=str(file_in),
676
+ out_path=str(file_out),
677
+ params_toml=params_toml,
678
+ scan_translator=scan_translator,
679
+ verbose=verbose,
680
+ ms1_molecular_search=ms1_molecular_search,
681
+ )
682
+ mz_dicts.append(mz_dict)
683
+ elif cores > 1:
684
+ pool = Pool(cores)
685
+ args = [
686
+ (
687
+ str(file_in),
688
+ str(file_out),
689
+ params_toml,
690
+ scan_translator,
691
+ verbose,
692
+ ms1_molecular_search,
693
+ )
694
+ for file_in, file_out in list(zip(files_list, out_paths_list))
695
+ ]
696
+ mz_dicts = pool.starmap(run_lipid_sp_ms1, args)
697
+ pool.close()
698
+ pool.join()
699
+
700
+ # Skip ms2 spectral search for now, will add back once we have an improved MetabRefLCInterface method
701
+ """
702
+ # Prepare ms2 spectral search space
703
+ metadata = prep_metadata(mz_dicts, out_dir, db_location)
704
+
705
+ # Run ms2 spectral search and export final results
706
+ if cores == 1 or len(files_list) == 1:
707
+ for file_out in out_paths_list:
708
+ mz_dicts = run_lipid_ms2(
709
+ file_out, metadata, scan_translator=scan_translator
710
+ )
711
+ elif cores > 1:
712
+ pool = Pool(cores)
713
+ args = [(file_out, metadata, scan_translator) for file_out in out_paths_list]
714
+ mz_dicts = pool.starmap(run_lipid_ms2, args)
715
+ pool.close()
716
+ pool.join()
717
+ """
718
+ print("Finished processing, data are written in " + str(out_dir))
719
+
720
+
721
+ if __name__ == "__main__":
722
+ # Set input variables to run
723
+ cores = 5
724
+ file_dir = Path("/Volumes/LaCie/nmdc_data/collection_testing/blanchard_lipid/mini_collection_test")
725
+ out_dir = Path("/Volumes/LaCie/nmdc_data/collection_testing/blanchard_lipid/mini_collection_test_out2")
726
+ params_toml = Path(
727
+ "/Volumes/LaCie/nmdc_data/collection_testing/blanchard_lipid/blanchard_collection_params.toml"
728
+ )
729
+ db_location = Path("tests/tests_data/lcms/202412_lipid_ref.sqlite")
730
+ verbose = True
731
+ scan_translator = Path("tmp_data/thermo_raw_collection/scan_translator.toml")
732
+
733
+ # Set up output directory
734
+ out_dir.mkdir(parents=True, exist_ok=True)
735
+
736
+ # if cores > 1, don't use verbose output
737
+ if cores > 2:
738
+ verbose = False
739
+
740
+ run_lipid_workflow(
741
+ file_dir=file_dir,
742
+ out_dir=out_dir,
743
+ params_toml=params_toml,
744
+ db_location=db_location,
745
+ scan_translator=scan_translator,
746
+ verbose=verbose,
747
+ cores=cores,
748
+ )