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,628 @@
1
+ from pathlib import Path
2
+ import time
3
+ import pandas as pd
4
+ import numpy as np
5
+ from multiprocessing import Pool
6
+ import shutil
7
+
8
+ from corems.mass_spectra.input.corems_hdf5 import ReadCoreMSHDFMassSpectraCollection
9
+ from corems.mass_spectra.output.export import LCMSMetabolomicsExport
10
+ from corems.mass_spectra.input.rawFileReader import ImportMassSpectraThermoMSFileReader
11
+ from corems.encapsulation.factory.parameters import LCMSParameters
12
+ from corems.molecular_id.search.database_interfaces import MSPInterface
13
+ from corems.encapsulation.factory.parameters import hush_output
14
+ from corems.mass_spectra.output.export import LCMSCollectionExport
15
+
16
+ """
17
+ Example showing the new pipeline-based sample processing approach.
18
+
19
+ The new approach combines multiple sample-level operations (gap-filling,
20
+ feature reloading, MS1/MS2 searches, etc.) into a single parallelized pass,
21
+ which is more efficient than processing samples multiple times.
22
+
23
+ Two usage patterns:
24
+ 1. High-level convenience method: process_consensus_features()
25
+ 2. Advanced pipeline builder: process_samples_pipeline() with custom operations
26
+ """
27
+
28
+ def summarize_processing_results(lcms_collection):
29
+ """
30
+ Summarize the processing state of the LCMS collection.
31
+
32
+ Reports on completed processing steps by inspecting the collection
33
+ and sample objects directly. Useful for verifying which operations
34
+ were performed during process_consensus_features().
35
+
36
+ Parameters
37
+ ----------
38
+ lcms_collection : LCMSCollection
39
+ The LCMS collection to summarize
40
+ """
41
+ print("\n" + "="*60)
42
+ print("LCMS Collection Processing Summary")
43
+ print("="*60)
44
+
45
+ # Basic collection info
46
+ n_samples = len(lcms_collection)
47
+
48
+ # Collection-level feature count (from dataframe - all features)
49
+ collection_mf_count = len(lcms_collection.mass_features_dataframe) if lcms_collection.mass_features_dataframe is not None else 0
50
+
51
+ # Sample-level loaded feature count (from mass_features dict - loaded for processing)
52
+ loaded_mf_count = sum(len(lcms_obj.mass_features) for lcms_obj in lcms_collection)
53
+
54
+ # Cluster count
55
+ if 'cluster' in lcms_collection.mass_features_dataframe.columns:
56
+ n_clusters = lcms_collection.mass_features_dataframe['cluster'].nunique()
57
+ else:
58
+ n_clusters = 0
59
+
60
+ print(f"\nSamples: {n_samples}")
61
+ print(f"Total features in collection: {collection_mf_count}")
62
+ print(f"Representative features loaded: {loaded_mf_count}")
63
+ if n_clusters > 0:
64
+ print(f"Consensus clusters: {n_clusters}")
65
+
66
+ # Gap filling - check for induced mass features
67
+ induced_counts = [len(lcms_obj.induced_mass_features) for lcms_obj in lcms_collection]
68
+ total_induced = sum(induced_counts)
69
+ samples_with_induced = sum(1 for c in induced_counts if c > 0)
70
+ if total_induced > 0:
71
+ print(f"\nGap Filling: ✓ Complete")
72
+ print(f" {samples_with_induced}/{n_samples} samples have induced features ({total_induced} total)")
73
+
74
+ # Feature loading - check if mass features have MS1/MS2 spectra
75
+ mf_with_ms1 = 0
76
+ mf_with_ms2 = 0
77
+ mf_with_ms2_scans = 0
78
+ total_ms2_spectra = 0
79
+ total_ms2_scan_numbers = 0
80
+
81
+ for lcms_obj in lcms_collection:
82
+ for mf in lcms_obj.mass_features.values():
83
+ if hasattr(mf, 'mass_spectrum') and mf.mass_spectrum is not None:
84
+ mf_with_ms1 += 1
85
+ if hasattr(mf, 'ms2_mass_spectra') and mf.ms2_mass_spectra:
86
+ mf_with_ms2 += 1
87
+ total_ms2_spectra += len(mf.ms2_mass_spectra)
88
+ if hasattr(mf, 'ms2_scan_numbers') and mf.ms2_scan_numbers is not None and len(mf.ms2_scan_numbers) > 0:
89
+ mf_with_ms2_scans += 1
90
+ total_ms2_scan_numbers += len(mf.ms2_scan_numbers)
91
+
92
+ if mf_with_ms1 > 0 or mf_with_ms2 > 0:
93
+ print(f"\nMS Data Association: ✓ Complete")
94
+ if mf_with_ms1 > 0:
95
+ print(f" MS1: {mf_with_ms1}/{loaded_mf_count} loaded features ({mf_with_ms1/loaded_mf_count*100:.1f}%)")
96
+ if mf_with_ms2 > 0:
97
+ print(f" MS2: {mf_with_ms2}/{loaded_mf_count} loaded features ({total_ms2_spectra} spectra)")
98
+ if mf_with_ms2_scans > 0:
99
+ print(f" MS2 scan numbers: {mf_with_ms2_scans}/{loaded_mf_count} loaded features ({total_ms2_scan_numbers} scans)")
100
+
101
+ # Molecular formula search
102
+ mf_with_formulas = 0
103
+ total_formulas = 0
104
+
105
+ for lcms_obj in lcms_collection:
106
+ for mf in lcms_obj.mass_features.values():
107
+ if hasattr(mf, 'mass_spectrum') and mf.mass_spectrum is not None:
108
+ try:
109
+ ms1_peak = mf.ms1_peak
110
+ if hasattr(ms1_peak, 'molecular_formulas') and ms1_peak.molecular_formulas:
111
+ mf_with_formulas += 1
112
+ total_formulas += len(ms1_peak.molecular_formulas)
113
+ except (AttributeError, IndexError):
114
+ pass
115
+
116
+ if mf_with_formulas > 0:
117
+ print(f"\nMolecular Formula Search: ✓ Complete")
118
+ print(f" {mf_with_formulas}/{loaded_mf_count} loaded features assigned ({total_formulas} total formulas)")
119
+ print(f" Average {total_formulas/mf_with_formulas:.1f} formulas per feature")
120
+
121
+ # MS2 spectral search
122
+ mf_with_spectral_matches = 0
123
+ total_spectral_matches = 0
124
+ scans_searched = 0
125
+
126
+ for lcms_obj in lcms_collection:
127
+ if hasattr(lcms_obj, 'spectral_search_results') and lcms_obj.spectral_search_results:
128
+ scans_searched += len(lcms_obj.spectral_search_results)
129
+
130
+ for mf in lcms_obj.mass_features.values():
131
+ if hasattr(mf, 'ms2_similarity_results') and mf.ms2_similarity_results:
132
+ mf_with_spectral_matches += 1
133
+ total_spectral_matches += len(mf.ms2_similarity_results)
134
+
135
+ if mf_with_spectral_matches > 0:
136
+ print(f"\nMS2 Spectral Search: ✓ Complete")
137
+ print(f" {scans_searched} MS2 scans with library search results")
138
+ print(f" {mf_with_spectral_matches}/{loaded_mf_count} loaded features matched ({total_spectral_matches} total matches)")
139
+ if hasattr(lcms_collection, 'spectral_search_molecular_metadata'):
140
+ print(f" Library size: {len(lcms_collection.spectral_search_molecular_metadata)} entries")
141
+
142
+ # Check for loaded EICs
143
+ total_eics_loaded = 0
144
+ samples_with_eics = 0
145
+
146
+ for lcms_obj in lcms_collection:
147
+ if hasattr(lcms_obj, 'eics') and lcms_obj.eics:
148
+ samples_with_eics += 1
149
+ total_eics_loaded += len(lcms_obj.eics)
150
+
151
+ if total_eics_loaded > 0:
152
+ print(f"\nEIC Loading: ✓ Complete")
153
+ print(f" {total_eics_loaded} EICs loaded across {samples_with_eics}/{n_samples} samples")
154
+ print(f" Average {total_eics_loaded/samples_with_eics:.1f} EICs per sample")
155
+
156
+ # Memory management check
157
+ raw_data_present = any(1 in lcms_obj._ms_unprocessed and not lcms_obj._ms_unprocessed[1].empty
158
+ for lcms_obj in lcms_collection)
159
+ if not raw_data_present:
160
+ print(f"\nMemory: ✓ Raw MS1 data cleaned")
161
+
162
+ print("\n" + "="*60)
163
+
164
+
165
+ def validate_save_load(lcms_collection, collection_save_path, ncores=1):
166
+ """
167
+ Validate that the LCMS collection can be saved and reloaded correctly.
168
+
169
+ This function tests the save/load functionality by reloading the collection
170
+ from HDF5 and comparing various attributes to ensure data integrity.
171
+
172
+ Parameters
173
+ ----------
174
+ lcms_collection : LCMSCollection
175
+ The original LCMS collection to validate against
176
+ collection_save_path : Path
177
+ Path to the saved collection HDF5 file (without extension)
178
+ ncores : int, optional
179
+ Number of cores to use for loading. Default is 1.
180
+
181
+ Returns
182
+ -------
183
+ LCMSCollection
184
+ The reloaded LCMS collection for further testing
185
+ """
186
+ print("\n" + "="*60)
187
+ print("SAVE/LOAD VALIDATION TEST")
188
+ print("="*60)
189
+
190
+ # Reload the collection from HDF5
191
+ from corems.mass_spectra.input.corems_hdf5 import ReadSavedLCMSCollection
192
+ reader = ReadSavedLCMSCollection(
193
+ collection_hdf5_path=str(collection_save_path.with_suffix('.hdf5')),
194
+ cores=ncores)
195
+ lcms_collection2 = reader.get_lcms_collection(
196
+ load_raw=False, load_light=True,
197
+ load_representatives=True, load_eics=True,
198
+ load_ms1=True, load_ms2=True)
199
+
200
+ # Check that len of mass features matches
201
+ mf_count_1 = len(lcms_collection.mass_features_dataframe)
202
+ mf_count_2 = len(lcms_collection2.mass_features_dataframe)
203
+ if mf_count_1 == mf_count_2:
204
+ print(f"✓ Mass feature count matches after reload: {mf_count_1}")
205
+ else:
206
+ print(f"✗ Mass feature count mismatch after reload! Original: {mf_count_1}, Reloaded: {mf_count_2}")
207
+
208
+ # Check that the len of induced mass features matches
209
+ induced_count_1 = len(lcms_collection.induced_mass_features_dataframe)
210
+ induced_count_2 = len(lcms_collection2.induced_mass_features_dataframe)
211
+ if induced_count_1 == induced_count_2:
212
+ print(f"✓ Induced mass feature count matches after reload: {induced_count_1}")
213
+ else:
214
+ print(f"✗ Induced mass feature count mismatch after reload! Original: {induced_count_1}, Reloaded: {induced_count_2}")
215
+
216
+ # Check that the len of loaded EICs matches
217
+ total_eics_1 = sum(len(lcms_obj.eics) for lcms_obj in lcms_collection)
218
+ total_eics_2 = sum(len(lcms_obj.eics) for lcms_obj in lcms_collection2)
219
+ if total_eics_1 == total_eics_2:
220
+ print(f"✓ Total loaded EIC count matches after reload: {total_eics_1}")
221
+ else:
222
+ print(f"✗ Total loaded EIC count mismatch after reload! Original: {total_eics_1}, Reloaded: {total_eics_2}")
223
+
224
+ # Check that the _ms dictionary matches (mass spectra loaded)
225
+ total_ms_1 = sum(len(lcms_obj._ms) for lcms_obj in lcms_collection)
226
+ total_ms_2 = sum(len(lcms_obj._ms) for lcms_obj in lcms_collection2)
227
+ if total_ms_1 == total_ms_2:
228
+ print(f"✓ Total loaded mass spectra (_ms) count matches after reload: {total_ms_1}")
229
+ else:
230
+ print(f"✗ Total loaded mass spectra (_ms) count mismatch after reload! Original: {total_ms_1}, Reloaded: {total_ms_2}")
231
+
232
+ # Check that we can replot the first cluster from the reloaded collection
233
+ if len(lcms_collection2.cluster_summary_dataframe) > 0:
234
+ first_cluster_id = lcms_collection2.cluster_summary_dataframe.index[0]
235
+ print(f"Re-plotting cluster {first_cluster_id} from reloaded collection")
236
+ lcms_collection2.plot_cluster(
237
+ cluster_id=first_cluster_id,
238
+ to_plot=["EIC", "MS1", "MS2"],
239
+ plot_smoothed_eic=False,
240
+ plot_eic_datapoints=False
241
+ )
242
+
243
+ # Check that scan numbers in _ms match for first sample
244
+ if len(lcms_collection) > 0 and len(lcms_collection2) > 0:
245
+ ms_scans_1 = set(lcms_collection[0]._ms.keys())
246
+ ms_scans_2 = set(lcms_collection2[0]._ms.keys())
247
+ if ms_scans_1 == ms_scans_2:
248
+ print(f"✓ Mass spectra scan numbers match for first sample: {len(ms_scans_1)} scans")
249
+ else:
250
+ print(f"✗ Mass spectra scan numbers mismatch for first sample!")
251
+ print(f" Original: {len(ms_scans_1)} scans, Reloaded: {len(ms_scans_2)} scans")
252
+ only_in_1 = ms_scans_1 - ms_scans_2
253
+ only_in_2 = ms_scans_2 - ms_scans_1
254
+ if only_in_1:
255
+ print(f" Only in original: {list(sorted(only_in_1))[:10]}...")
256
+ if only_in_2:
257
+ print(f" Only in reloaded: {list(sorted(only_in_2))[:10]}...")
258
+
259
+ print("\n" + "="*60)
260
+
261
+ return lcms_collection2
262
+
263
+
264
+ def preprocess_raw_samples(raw_data_path, processed_folder, ncores=1, reprocess=False):
265
+ """
266
+ Preprocess raw LCMS sample files into HDF5 format.
267
+
268
+ Parameters
269
+ ----------
270
+ raw_data_path : Path
271
+ Path to folder containing raw data files
272
+ processed_folder : Path
273
+ Path to folder where processed HDF5 files will be saved
274
+ ncores : int, optional
275
+ Number of cores to use for parallel processing. Default is 1.
276
+ reprocess : bool, optional
277
+ If True, deletes existing processed folder and reprocesses all files.
278
+ If False, skips preprocessing. Default is False.
279
+
280
+ Returns
281
+ -------
282
+ list or None
283
+ List of processed HDF5 file paths if reprocess=True, None otherwise
284
+ """
285
+ if not reprocess:
286
+ print("\n=== Skipping sample preprocessing (using existing processed data) ===")
287
+ return None
288
+
289
+ # Delete existing processed dir if reprocessing
290
+ if processed_folder.exists():
291
+ shutil.rmtree(processed_folder)
292
+
293
+ # Create processed folder
294
+ processed_folder.mkdir(parents=True, exist_ok=True)
295
+
296
+ # Find all raw files (adjust extension based on your data format)
297
+ raw_files = list(raw_data_path.glob("*.raw")) + list(raw_data_path.glob("*.mzML"))
298
+
299
+ if not raw_files:
300
+ raise ValueError(f"No raw files found in {raw_data_path}")
301
+
302
+ print(f"\n=== Preprocessing {len(raw_files)} samples in parallel using {ncores} cores ===")
303
+ start_time = time.time()
304
+
305
+ # Get configured parameters once (will be shared across all workers)
306
+ params = get_configured_lcms_parameters()
307
+
308
+ # Prepare arguments for parallel processing
309
+ process_args = [(raw_file, processed_folder, params) for raw_file in raw_files]
310
+
311
+ # Process samples in parallel
312
+ with Pool(processes=ncores) as pool:
313
+ processed_files = pool.map(process_single_sample, process_args)
314
+
315
+ print(f"Preprocessing complete: {time.time() - start_time:.1f} seconds using {ncores} cores")
316
+ print(f"Processed {len(processed_files)} samples\n")
317
+
318
+ return processed_files
319
+
320
+
321
+ def get_configured_lcms_parameters():
322
+ """
323
+ Create and configure LCMSParameters for sample processing.
324
+
325
+ Returns
326
+ -------
327
+ LCMSParameters
328
+ Configured parameters object with all processing settings
329
+ """
330
+ # Suppress verbose output before creating parameters
331
+ hush_output()
332
+
333
+ # Create parameters (use_defaults=False respects hush_output)
334
+ params = LCMSParameters()
335
+
336
+ # Persistent homology parameters
337
+ params.lc_ms.peak_picking_method = "persistent homology"
338
+ params.lc_ms.ph_inten_min_rel = 0.0005
339
+ params.lc_ms.ph_persis_min_rel = 0.01
340
+ params.lc_ms.ph_smooth_it = 0
341
+ params.lc_ms.ms2_min_fe_score = 0.3
342
+ params.lc_ms.ms1_scans_to_average = 1
343
+
344
+ # MSParameters for ms1 mass spectra
345
+ ms1_params = params.mass_spectrum['ms1']
346
+ ms1_params.mass_spectrum.noise_threshold_method = "relative_abundance"
347
+ ms1_params.mass_spectrum.noise_threshold_min_relative_abundance = 0.1
348
+ ms1_params.mass_spectrum.noise_min_mz = 0
349
+ ms1_params.mass_spectrum.min_picking_mz = 0
350
+ ms1_params.mass_spectrum.noise_max_mz = np.inf
351
+ ms1_params.mass_spectrum.max_picking_mz = np.inf
352
+ ms1_params.ms_peak.legacy_resolving_power = False
353
+ ms1_params.molecular_search.url_database = ""
354
+ ms1_params.molecular_search.usedAtoms = {
355
+ 'C': (5, 30),
356
+ 'H': (18, 200),
357
+ 'O': (1, 23),
358
+ 'N': (0, 3),
359
+ 'P': (0, 1),
360
+ 'S': (0, 1),
361
+ }
362
+
363
+ # Settings for ms2 data (HCD scans)
364
+ ms2_params_hcd = ms1_params.copy()
365
+ params.mass_spectrum['ms2'] = ms2_params_hcd
366
+
367
+ # Reporting settings
368
+ params.lc_ms.export_eics = True
369
+ params.lc_ms.export_profile_spectra = False
370
+
371
+ # Peak metrics filtering settings
372
+ params.lc_ms.remove_mass_features_by_peak_metrics = True
373
+ params.lc_ms.mass_feature_attribute_filter_dict = {
374
+ 'dispersity_index': {'value': 0.5, 'operator': '<'}
375
+ }
376
+
377
+ return params
378
+
379
+
380
+ def process_single_sample(args):
381
+ """
382
+ Process a single LCMS sample file.
383
+
384
+ Parameters, params)
385
+
386
+ Returns
387
+ -------
388
+ str
389
+ Path to the processed HDF5 file
390
+ """
391
+ raw_file_path, processed_folder, params = args
392
+
393
+ # Import the raw data
394
+ print(f"Processing {raw_file_path.name}...\n")
395
+ parser = ImportMassSpectraThermoMSFileReader(str(raw_file_path))
396
+ lcms_obj = parser.get_lcms_obj(spectra="ms1")
397
+
398
+ # Use the pre-configured parameters
399
+ lcms_obj.parameters = params
400
+
401
+ # Get configured parameters
402
+ lcms_obj.parameters = get_configured_lcms_parameters()
403
+
404
+ # Use persistent homology to find mass features in the lc-ms data and integrate
405
+ # Assign MS2 scan numbers during peak picking for choosing representatives with MS2
406
+ lcms_obj.find_mass_features(assign_ms2_scans=True, ms2_scan_filter=None)
407
+ lcms_obj.integrate_mass_features(drop_if_fail=True)
408
+
409
+ # Add peak metrics and filter mass features based on the new parameters
410
+ lcms_obj.add_peak_metrics(remove_by_metrics=True)
411
+
412
+ # Save to HDF5
413
+ output_name = raw_file_path.stem
414
+ exporter = LCMSMetabolomicsExport(str(processed_folder / output_name), lcms_obj)
415
+ exporter.to_hdf(overwrite=True)
416
+
417
+ print(f"✓ Completed {raw_file_path.name} - {len(lcms_obj.mass_features)} mass features")
418
+ return str(processed_folder / f"{output_name}.hdf5")
419
+
420
+ if __name__ == "__main__":
421
+ # =============================================================================
422
+ # Configuration
423
+ # =============================================================================
424
+ ncores = 1
425
+ reprocess_samples = False # Set to True to reprocess raw data
426
+ perform_ms2_search = True # Set to True to perform MS2 spectral library search
427
+
428
+ # Paths
429
+ base_path = Path("/Volumes/LaCie/nmdc_data/collection_testing/dev_test/")
430
+ collection_save_path = base_path / "collection"
431
+ raw_data_path = base_path / "raw"
432
+ processed_folder = base_path / "processed2"
433
+ msp_file_location = Path("/Users/heal742/LOCAL/05_NMDC/02_MetaMS/metams/test_data/test_lcms_metab_data/20250407_database.msp")
434
+ new_raw_data_path = raw_data_path # Update raw file paths in collection if moved after the first step
435
+
436
+ # =============================================================================
437
+ # Step 1: Preprocess Individual Samples (Optional)
438
+ # =============================================================================
439
+ if reprocess_samples:
440
+ print("\n=== Preprocessing Raw Samples and Doing Initial Peak Picking===")
441
+ preprocess_raw_samples(
442
+ raw_data_path=raw_data_path,
443
+ processed_folder=processed_folder,
444
+ ncores=ncores,
445
+ reprocess=reprocess_samples
446
+ )
447
+
448
+ # =============================================================================
449
+ # Step 2: Load LCMS Collection
450
+ # =============================================================================
451
+ print("\n=== Loading LCMS Collection ===")
452
+ parser = ReadCoreMSHDFMassSpectraCollection(
453
+ folder_location=processed_folder,
454
+ cores=ncores
455
+ )
456
+ print(f"Found {len(parser.manifest)} samples")
457
+
458
+ # Load collection (light loading for efficiency)
459
+ start_time = time.time()
460
+ lcms_collection = parser.get_lcms_collection(load_raw=False, load_light=True)
461
+ print(f"Loaded in {time.time() - start_time:.1f} seconds")
462
+ print(f"Total mass features: {len(lcms_collection.mass_features_dataframe)}")
463
+
464
+ # Update raw file locations
465
+ lcms_collection.update_raw_file_locations(new_raw_folder=str(new_raw_data_path))
466
+
467
+ # =============================================================================
468
+ # Step 3: Align Retention Times Across Samples
469
+ # =============================================================================
470
+ print("\n=== Aligning Retention Times ===")
471
+ start_time = time.time()
472
+ lcms_collection.align_lcms_objects()
473
+ print(f"Alignment complete: {time.time() - start_time:.1f} seconds")
474
+
475
+ # =============================================================================
476
+ # Step 4: Generate Consensus Mass Features
477
+ # =============================================================================
478
+ print("\n=== Generating Consensus Mass Features ===")
479
+ start_time = time.time()
480
+ lcms_collection.add_consensus_mass_features()
481
+ print(f"Generated {len(lcms_collection.cluster_summary_dataframe)} consensus clusters")
482
+ print(f"Consensus generation: {time.time() - start_time:.1f} seconds")
483
+
484
+ # =============================================================================
485
+ # Step 5: Prepare MS2 Spectral Library
486
+ # =============================================================================
487
+ if perform_ms2_search:
488
+ print("\n=== Preparing MS2 Spectral Library ===")
489
+ my_msp = MSPInterface(file_path=msp_file_location)
490
+ spectral_lib, molecular_metadata = my_msp.get_metabolomics_spectra_library(
491
+ polarity="positive",
492
+ format="flashentropy",
493
+ normalize=True,
494
+ fe_kwargs={
495
+ "normalize_intensity": True,
496
+ "min_ms2_difference_in_da": 0.02,
497
+ "max_ms2_tolerance_in_da": 0.01,
498
+ "max_indexed_mz": 3000,
499
+ "precursor_ions_removal_da": None,
500
+ "noise_threshold": 0,
501
+ },
502
+ )
503
+ print(f"Loaded spectral library: {len(molecular_metadata)} entries")
504
+ else:
505
+ spectral_lib = None
506
+ molecular_metadata = None
507
+ print("Skipping MS2 spectral library preparation")
508
+
509
+ # =============================================================================
510
+ # Step 6: Process Consensus Features with Integrated Pipeline
511
+ # =============================================================================
512
+ print("\n=== Processing Consensus Features ===")
513
+ start_time = time.time()
514
+ pipeline_results = lcms_collection.process_consensus_features(
515
+ load_representatives=True,
516
+ perform_gap_filling=True,
517
+ add_ms1=True,
518
+ add_ms2=True,
519
+ molecular_formula_search=True,
520
+ ms2_spectral_search=perform_ms2_search,
521
+ spectral_lib=spectral_lib,
522
+ molecular_metadata=molecular_metadata,
523
+ gather_eics=True,
524
+ keep_raw_data=False
525
+ )
526
+ print(f"Pipeline complete: {time.time() - start_time:.1f} seconds using {ncores} cores")
527
+
528
+ # =============================================================================
529
+ # Step 6.5: Test Consensus Cluster Plotting
530
+ # =============================================================================
531
+ print("\n=== Testing Consensus Cluster Plotting ===")
532
+
533
+ # Plot the first cluster as a test
534
+ if len(lcms_collection.cluster_summary_dataframe) > 0:
535
+ first_cluster_id = lcms_collection.cluster_summary_dataframe.index[0]
536
+ # 3116 is a good one to look at :)
537
+ print(f"Plotting cluster {first_cluster_id}")
538
+ lcms_collection.plot_cluster(
539
+ cluster_id=first_cluster_id,
540
+ to_plot=["EIC", "MS1", "MS2"],
541
+ plot_smoothed_eic=False,
542
+ plot_eic_datapoints=False
543
+ )
544
+
545
+ # =============================================================================
546
+ # Step 7: Summarize Processing Results
547
+ # =============================================================================
548
+ summarize_processing_results(lcms_collection)
549
+
550
+
551
+ # =============================================================================
552
+ # Step 8: Save and Export Results
553
+ # =============================================================================
554
+ print("\n=== Exporting LCMS Collection ===")
555
+ # Create pivot tables summarizing the collection across samples
556
+ pivot_table_intensity = lcms_collection.collection_pivot_table(attribute='intensity', verbose=False)
557
+ pivot_table_ids = lcms_collection.collection_pivot_table(verbose=False)
558
+ pivot_table_intensity.to_csv("example_collection_pivot_intensity.csv")
559
+
560
+ # Describe each cluster with its representative mass feature
561
+ cluster_reps = lcms_collection.cluster_representatives_table()
562
+ cluster_reps.to_csv("example_cluster_representatives.csv", index=False)
563
+ print(f"Cluster representatives table: {len(cluster_reps)} clusters")
564
+
565
+ # Summarize the annotations for each cluster
566
+ feature_annotations = lcms_collection.feature_annotations_table(
567
+ molecular_metadata=molecular_metadata,
568
+ drop_unannotated=True
569
+ )
570
+ print(f"Feature annotations table: {len(feature_annotations)} rows across {feature_annotations['cluster'].nunique()} clusters")
571
+
572
+
573
+ # Plot the first mass feature with an annotation with MS2_mirror
574
+ if not feature_annotations.empty:
575
+ # Find first annotated cluster with non-null value in ref_ms_id
576
+ first_annotated_cluster = feature_annotations.loc[
577
+ feature_annotations['ref_ms_id'].notnull(), 'cluster'
578
+ ].iloc[0]
579
+ print(f"Plotting annotated feature for cluster {first_annotated_cluster}")
580
+ lcms_collection.plot_cluster(
581
+ cluster_id=first_annotated_cluster,
582
+ to_plot=["EIC", "MS1", "MS2_mirror"],
583
+ molecular_metadata=molecular_metadata,
584
+ spectral_library=spectral_lib
585
+ )
586
+
587
+ # Save the feature annotations table to CSV for inspection
588
+ feature_annotations.to_csv("example_feature_annotations.csv", index=False)
589
+
590
+ # Save the entire collection to HDF5
591
+ exporter = LCMSCollectionExport(
592
+ out_file_path=str(collection_save_path),
593
+ mass_spectra_collection=lcms_collection)
594
+ exporter.export_to_hdf5(overwrite=True, save_parameters=True, parameter_format="toml")
595
+
596
+ # =============================================================================
597
+ # Step 9: Validate Save/Load Functionality
598
+ # =============================================================================
599
+ lcms_collection2 = validate_save_load(lcms_collection, collection_save_path, ncores=ncores)
600
+
601
+
602
+ """
603
+ # Make some more plots
604
+ lcms_collection.plot_mz_features_across_samples()
605
+ lcms_collection.plot_mz_features_per_cluster()
606
+ lcms_collection.plot_consensus_mz_features() ## zoomed out
607
+ lcms_collection.plot_consensus_mz_features(xb = 10, xt = 15, yb = 500, yt = 600) ## zoomed in
608
+ lcms_collection.cluster_inspection_plot(0)
609
+
610
+ dim_list = [
611
+ 'mz',
612
+ 'scan_time_aligned',
613
+ 'half_height_width',
614
+ 'tailing_factor',
615
+ 'dispersity_index',
616
+ 'intensity',
617
+ 'persistence'
618
+ ]
619
+ lcms_collection.plot_cluster_outlier_frequency(dim_list, clu_size_thresh = 0.25)
620
+
621
+ # Create pivot tables and reports
622
+ results = lcms_collection.collection_pivot_table()
623
+ results1 = lcms_collection.collection_pivot_table(attribute = 'intensity')
624
+ results2 = lcms_collection.collection_consensus_report(how = 'intensity')
625
+ results3 = lcms_collection.collection_consensus_report(how = 'median')
626
+
627
+ #TODO KRH: Add visualization of matched spectrum with consensus mass feature
628
+ """
@@ -0,0 +1,42 @@
1
+ import requests
2
+ import os
3
+ import json
4
+ from pathlib import Path
5
+
6
+
7
+ username = os.environ.get('DMS_USER_NAME') or 'XXXX'
8
+ password = os.environ.get('DMS_USER_PASSWORD') or 'XXXXX'
9
+
10
+ s = requests.Session()
11
+ s.auth = (username, password)
12
+
13
+ url_request = "https://dms2.pnl.gov/data/ax/json/list_report/dataset/{}".format("4-AA_L-AsparticAcid")
14
+ r = s.get(url_request)
15
+
16
+ data_list = r.json()
17
+ for data in data_list:
18
+ print(data)
19
+
20
+
21
+ filein = Path("data/ftms_nom_data_products.json")
22
+ outfile = Path("data/ftms_nom_nmdc_data.csv")
23
+
24
+ with outfile.open("w") as csv_data:
25
+
26
+ csv_data.write('{},{} \n'.format('DMS Dataset ID', "Dataset"))
27
+
28
+ with filein.open() as json_data:
29
+
30
+ list_data = json.load(json_data)
31
+ for data in list_data:
32
+ filename = (data.get("name").replace(".csv", ""))
33
+ if filename[0:6] == 'Brodie':
34
+
35
+ url_request = "https://dms2.pnl.gov/data/ax/json/list_report/dataset/{}".format(filename)
36
+ r = s.get(url_request)
37
+
38
+ data_list = r.json()
39
+ for data in data_list:
40
+ (data.get('ID'))
41
+
42
+ csv_data.write('{},{}\n'.format(data.get('ID'), filename))