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.
- corems/__init__.py +63 -0
- corems/chroma_peak/__init__.py +0 -0
- corems/chroma_peak/calc/ChromaPeakCalc.py +480 -0
- corems/chroma_peak/calc/__init__.py +0 -0
- corems/chroma_peak/calc/subset.py +196 -0
- corems/chroma_peak/factory/__init__.py +0 -0
- corems/chroma_peak/factory/chroma_peak_classes.py +1178 -0
- corems/encapsulation/__init__.py +0 -0
- corems/encapsulation/constant.py +1283 -0
- corems/encapsulation/factory/__init__.py +0 -0
- corems/encapsulation/factory/parameters.py +392 -0
- corems/encapsulation/factory/processingSetting.py +1244 -0
- corems/encapsulation/input/__init__.py +0 -0
- corems/encapsulation/input/parameter_from_json.py +579 -0
- corems/encapsulation/output/__init__.py +0 -0
- corems/encapsulation/output/parameter_to_dict.py +142 -0
- corems/encapsulation/output/parameter_to_json.py +332 -0
- corems/mass_spectra/__init__.py +0 -0
- corems/mass_spectra/calc/GC_Calc.py +84 -0
- corems/mass_spectra/calc/GC_Deconvolution.py +558 -0
- corems/mass_spectra/calc/GC_RI_Calibration.py +44 -0
- corems/mass_spectra/calc/MZSearch.py +249 -0
- corems/mass_spectra/calc/SignalProcessing.py +656 -0
- corems/mass_spectra/calc/__init__.py +0 -0
- corems/mass_spectra/calc/lc_calc.py +5790 -0
- corems/mass_spectra/calc/lc_calc_operations.py +1127 -0
- corems/mass_spectra/factory/GC_Class.py +791 -0
- corems/mass_spectra/factory/__init__.py +0 -0
- corems/mass_spectra/factory/chromat_data.py +51 -0
- corems/mass_spectra/factory/lc_class.py +2721 -0
- corems/mass_spectra/input/__init__.py +0 -0
- corems/mass_spectra/input/andiNetCDF.py +200 -0
- corems/mass_spectra/input/boosterHDF5.py +216 -0
- corems/mass_spectra/input/brukerSolarix.py +153 -0
- corems/mass_spectra/input/brukerSolarix_utils.py +73 -0
- corems/mass_spectra/input/corems_hdf5.py +1710 -0
- corems/mass_spectra/input/massList.py +133 -0
- corems/mass_spectra/input/mzml.py +668 -0
- corems/mass_spectra/input/parserbase.py +239 -0
- corems/mass_spectra/input/rawFileReader.py +1839 -0
- corems/mass_spectra/output/__init__.py +0 -0
- corems/mass_spectra/output/export.py +2800 -0
- corems/mass_spectrum/__init__.py +0 -0
- corems/mass_spectrum/calc/AutoRecalibration.py +237 -0
- corems/mass_spectrum/calc/Calibration.py +602 -0
- corems/mass_spectrum/calc/CalibrationCalc.py +253 -0
- corems/mass_spectrum/calc/KendrickGroup.py +239 -0
- corems/mass_spectrum/calc/MassErrorPrediction.py +436 -0
- corems/mass_spectrum/calc/MassSpectrumCalc.py +303 -0
- corems/mass_spectrum/calc/MeanResolvingPowerFilter.py +212 -0
- corems/mass_spectrum/calc/NoiseCalc.py +371 -0
- corems/mass_spectrum/calc/NoiseCalc_Bayes.py +93 -0
- corems/mass_spectrum/calc/PeakPicking.py +994 -0
- corems/mass_spectrum/calc/__init__.py +0 -0
- corems/mass_spectrum/factory/MassSpectrumClasses.py +1753 -0
- corems/mass_spectrum/factory/__init__.py +0 -0
- corems/mass_spectrum/input/__init__.py +0 -0
- corems/mass_spectrum/input/baseClass.py +531 -0
- corems/mass_spectrum/input/boosterHDF5.py +161 -0
- corems/mass_spectrum/input/coremsHDF5.py +475 -0
- corems/mass_spectrum/input/massList.py +402 -0
- corems/mass_spectrum/input/numpyArray.py +133 -0
- corems/mass_spectrum/output/__init__.py +0 -0
- corems/mass_spectrum/output/export.py +841 -0
- corems/molecular_formula/__init__.py +0 -0
- corems/molecular_formula/calc/MolecularFormulaCalc.py +885 -0
- corems/molecular_formula/calc/__init__.py +0 -0
- corems/molecular_formula/factory/MolecularFormulaFactory.py +895 -0
- corems/molecular_formula/factory/__init__.py +0 -0
- corems/molecular_formula/input/__init__.py +0 -0
- corems/molecular_formula/input/masslist_ref.py +355 -0
- corems/molecular_id/__init__.py +0 -0
- corems/molecular_id/calc/ClusterFilter.py +251 -0
- corems/molecular_id/calc/MolecularFilter.py +122 -0
- corems/molecular_id/calc/SpectralSimilarity.py +632 -0
- corems/molecular_id/calc/__init__.py +0 -0
- corems/molecular_id/calc/math_distance.py +1637 -0
- corems/molecular_id/data/FAMES_REF.msp +980 -0
- corems/molecular_id/data/PNNLMetV20191015.msp +157267 -0
- corems/molecular_id/factory/EI_SQL.py +650 -0
- corems/molecular_id/factory/MolecularLookupTable.py +914 -0
- corems/molecular_id/factory/__init__.py +0 -0
- corems/molecular_id/factory/classification.py +884 -0
- corems/molecular_id/factory/lipid_molecular_metadata.py +50 -0
- corems/molecular_id/factory/molecularSQL.py +827 -0
- corems/molecular_id/factory/spectrum_search_results.py +119 -0
- corems/molecular_id/input/__init__.py +0 -0
- corems/molecular_id/input/nistMSI.py +148 -0
- corems/molecular_id/search/__init__.py +0 -0
- corems/molecular_id/search/compoundSearch.py +214 -0
- corems/molecular_id/search/database_interfaces.py +1527 -0
- corems/molecular_id/search/findOxygenPeaks.py +330 -0
- corems/molecular_id/search/lcms_spectral_search.py +348 -0
- corems/molecular_id/search/molecularFormulaSearch.py +1117 -0
- corems/molecular_id/search/priorityAssignment.py +723 -0
- corems/ms_peak/__init__.py +0 -0
- corems/ms_peak/calc/MSPeakCalc.py +1010 -0
- corems/ms_peak/calc/__init__.py +0 -0
- corems/ms_peak/factory/MSPeakClasses.py +542 -0
- corems/ms_peak/factory/__init__.py +0 -0
- corems/transient/__init__.py +0 -0
- corems/transient/calc/TransientCalc.py +362 -0
- corems/transient/calc/__init__.py +0 -0
- corems/transient/factory/TransientClasses.py +457 -0
- corems/transient/factory/__init__.py +0 -0
- corems/transient/input/__init__.py +0 -0
- corems/transient/input/brukerSolarix.py +461 -0
- corems/transient/input/midasDatFile.py +172 -0
- corems-4.0.0.dist-info/METADATA +475 -0
- corems-4.0.0.dist-info/RECORD +171 -0
- corems-4.0.0.dist-info/WHEEL +5 -0
- corems-4.0.0.dist-info/licenses/LICENSE +22 -0
- corems-4.0.0.dist-info/top_level.txt +4 -0
- examples/archive/scripts/CoreMS_tutorial.py +94 -0
- examples/archive/scripts/DI HR-MS Halogens Bruker.py +196 -0
- examples/archive/scripts/DI HR-MS MassList.py +385 -0
- examples/archive/scripts/GC-MS MetabRef.py +213 -0
- examples/archive/scripts/GC-MS NetCDF.py +217 -0
- examples/archive/scripts/HR-MS Thermo Raw 21T.py +136 -0
- examples/archive/scripts/LC-ICPMS_metal_peaks.py +297 -0
- examples/archive/scripts/LCMS-Thermo.py +460 -0
- examples/archive/scripts/LCMS_isotopes.py +283 -0
- examples/archive/scripts/MSParams_example.py +21 -0
- examples/archive/scripts/Molecular Formula Data Aggreation.py +84 -0
- examples/archive/scripts/Single Mz Search.py +69 -0
- examples/test_notebooks.py +145 -0
- ext_lib/ChemstationMSFileReader.dll +0 -0
- ext_lib/ChemstationMSFileReader.xml +126 -0
- ext_lib/RawFileReaderLicense.doc +0 -0
- ext_lib/ThermoFisher.CommonCore.BackgroundSubtraction.dll +0 -0
- ext_lib/ThermoFisher.CommonCore.BackgroundSubtraction.xml +2307 -0
- ext_lib/ThermoFisher.CommonCore.Data.dll +0 -0
- ext_lib/ThermoFisher.CommonCore.Data.xml +28974 -0
- ext_lib/ThermoFisher.CommonCore.MassPrecisionEstimator.dll +0 -0
- ext_lib/ThermoFisher.CommonCore.MassPrecisionEstimator.xml +241 -0
- ext_lib/ThermoFisher.CommonCore.RawFileReader.dll +0 -0
- ext_lib/ThermoFisher.CommonCore.RawFileReader.xml +31174 -0
- ext_lib/__init__.py +0 -0
- ext_lib/dotnet/OpenMcdf.Extensions.dll +0 -0
- ext_lib/dotnet/OpenMcdf.dll +0 -0
- ext_lib/dotnet/OpenMcdf.xml +1154 -0
- ext_lib/dotnet/System.IO.FileSystem.AccessControl.dll +0 -0
- ext_lib/dotnet/System.IO.FileSystem.AccessControl.xml +506 -0
- ext_lib/dotnet/System.Security.AccessControl.dll +0 -0
- ext_lib/dotnet/System.Security.AccessControl.xml +2043 -0
- ext_lib/dotnet/System.Security.Principal.Windows.dll +0 -0
- ext_lib/dotnet/System.Security.Principal.Windows.xml +1011 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.BackgroundSubtraction.dll +0 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.BackgroundSubtraction.xml +2307 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.Data.dll +0 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.Data.xml +29148 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.MassPrecisionEstimator.dll +0 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.MassPrecisionEstimator.xml +241 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.RawFileReader.dll +0 -0
- ext_lib/dotnet/ThermoFisher.CommonCore.RawFileReader.xml +31492 -0
- ext_lib/version +18 -0
- support_code/atom_parsers/AtomsDescription.py +359 -0
- support_code/atom_parsers/CreateAtomsDescription.py +56 -0
- support_code/nmdc/filefinder.py +90 -0
- support_code/nmdc/lipidomics/lipidomics_workflow.py +748 -0
- support_code/nmdc/lipidomics/manifest_examples.py +49 -0
- support_code/nmdc/metabolomics/gcms_workflow.py +158 -0
- support_code/nmdc/metabolomics/lcms_metabolomics_targeted_search.py +59 -0
- support_code/nmdc/metabolomics/lcms_metabolomics_workflow.py +248 -0
- support_code/nmdc/metabolomics/metabolomics_collection.py +628 -0
- support_code/nmdc/metadata/dms_api.py +42 -0
- support_code/nmdc/nom/archived_scripts/nmdc_metadata_gen.py +288 -0
- support_code/nmdc/nom/archived_scripts/nom_grow_workflow.py +209 -0
- support_code/nmdc/nom/nom_workflow.py +312 -0
- support_code/windows_only_importers/BrukerCompassXtract.py +180 -0
- support_code/windows_only_importers/ThermoMSFileReader.py +405 -0
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, Union, List, Tuple
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import pymzml
|
|
8
|
+
import datetime
|
|
9
|
+
|
|
10
|
+
from corems.encapsulation.constant import Labels
|
|
11
|
+
from corems.encapsulation.factory.parameters import default_parameters
|
|
12
|
+
from corems.mass_spectra.factory.lc_class import LCMSBase, MassSpectraBase
|
|
13
|
+
from corems.mass_spectra.input.parserbase import SpectraParserInterface
|
|
14
|
+
from corems.mass_spectrum.factory.MassSpectrumClasses import (
|
|
15
|
+
MassSpecCentroid,
|
|
16
|
+
MassSpecProfile,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MZMLSpectraParser(SpectraParserInterface):
|
|
21
|
+
"""A class for parsing mzml spectrometry data files into MassSpectraBase or LCMSBase objects
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
file_location : str or Path
|
|
26
|
+
The path to the RAW file to be parsed.
|
|
27
|
+
analyzer : str, optional
|
|
28
|
+
The type of mass analyzer used in the instrument. Default is "Unknown".
|
|
29
|
+
instrument_label : str, optional
|
|
30
|
+
The name of the instrument used to acquire the data. Default is "Unknown".
|
|
31
|
+
sample_name : str, optional
|
|
32
|
+
The name of the sample being analyzed. If not provided, the stem of the file_location path will be used.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
file_location : Path
|
|
37
|
+
The path to the RAW file being parsed.
|
|
38
|
+
analyzer : str
|
|
39
|
+
The type of mass analyzer used in the instrument.
|
|
40
|
+
instrument_label : str
|
|
41
|
+
The name of the instrument used to acquire the data.
|
|
42
|
+
sample_name : str
|
|
43
|
+
The name of the sample being analyzed.
|
|
44
|
+
|
|
45
|
+
Methods
|
|
46
|
+
-------
|
|
47
|
+
* load().
|
|
48
|
+
Load mzML file using pymzml.run.Reader and return the data as a numpy array.
|
|
49
|
+
* run(spectra=True).
|
|
50
|
+
Parses the mzml file and returns a dictionary of mass spectra dataframes and a scan metadata dataframe.
|
|
51
|
+
* get_mass_spectrum_from_scan(scan_number, polarity, auto_process=True)
|
|
52
|
+
Parses the mzml file and returns a MassSpecBase object from a single scan.
|
|
53
|
+
* get_mass_spectra_obj().
|
|
54
|
+
Parses the mzml file and instantiates a MassSpectraBase object.
|
|
55
|
+
* get_lcms_obj().
|
|
56
|
+
Parses the mzml file and instantiates an LCMSBase object.
|
|
57
|
+
* get_instrument_info().
|
|
58
|
+
Return instrument information from the mzML file.
|
|
59
|
+
* get_creation_time().
|
|
60
|
+
Return the creation time of the mzML file as a datetime object.
|
|
61
|
+
|
|
62
|
+
Inherits from ThermoBaseClass and SpectraParserInterface
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
file_location,
|
|
68
|
+
analyzer="Unknown",
|
|
69
|
+
instrument_label="Unknown",
|
|
70
|
+
sample_name=None,
|
|
71
|
+
):
|
|
72
|
+
# implementation details
|
|
73
|
+
if isinstance(file_location, str):
|
|
74
|
+
# if obj is a string it defaults to create a Path obj, pass the S3Path if needed
|
|
75
|
+
file_location = Path(file_location)
|
|
76
|
+
if not file_location.exists():
|
|
77
|
+
raise FileExistsError("File does not exist: " + str(file_location))
|
|
78
|
+
self.file_location = file_location
|
|
79
|
+
self.analyzer = analyzer
|
|
80
|
+
self.instrument_label = instrument_label
|
|
81
|
+
|
|
82
|
+
if sample_name:
|
|
83
|
+
self.sample_name = sample_name
|
|
84
|
+
else:
|
|
85
|
+
self.sample_name = file_location.stem
|
|
86
|
+
|
|
87
|
+
def load(self):
|
|
88
|
+
"""
|
|
89
|
+
Load mzML file using pymzml.run.Reader and return the data as a numpy array.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
numpy.ndarray
|
|
94
|
+
The mass spectra data as a numpy array.
|
|
95
|
+
"""
|
|
96
|
+
data = pymzml.run.Reader(self.file_location)
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
def get_scan_df(self, data=None, time_range: Optional[Union[Tuple[float, float], List[Tuple[float, float]]]] = None):
|
|
100
|
+
"""
|
|
101
|
+
Return scan data as a pandas DataFrame.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
data : pymzml.run.Reader, optional
|
|
106
|
+
The mass spectra data. If None, will load the data.
|
|
107
|
+
time_range : tuple or list of tuples, optional
|
|
108
|
+
Retention time range(s) to filter scans. Can be:
|
|
109
|
+
- Single range: (start_time, end_time) in minutes
|
|
110
|
+
- Multiple ranges: [(start1, end1), (start2, end2), ...] in minutes
|
|
111
|
+
If None, returns all scans.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
pandas.DataFrame
|
|
116
|
+
A pandas DataFrame containing metadata for each scan, including scan number, MS level, polarity, and scan time.
|
|
117
|
+
"""
|
|
118
|
+
if data is None:
|
|
119
|
+
data = self.load()
|
|
120
|
+
# Scan dict
|
|
121
|
+
# instatinate scan dict, with empty lists of size of scans
|
|
122
|
+
n_scans = data.get_spectrum_count()
|
|
123
|
+
scan_dict = {
|
|
124
|
+
"scan": np.empty(n_scans, dtype=np.int32),
|
|
125
|
+
"scan_time": np.empty(n_scans, dtype=np.float32),
|
|
126
|
+
"ms_level": [None] * n_scans,
|
|
127
|
+
"polarity": [None] * n_scans,
|
|
128
|
+
"precursor_mz": [None] * n_scans,
|
|
129
|
+
"scan_text": [None] * n_scans,
|
|
130
|
+
"scan_window_lower": np.empty(n_scans, dtype=np.float32),
|
|
131
|
+
"scan_window_upper": np.empty(n_scans, dtype=np.float32),
|
|
132
|
+
"scan_precision": [None] * n_scans,
|
|
133
|
+
"tic": np.empty(n_scans, dtype=np.float32),
|
|
134
|
+
"ms_format": [None] * n_scans,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# First pass: loop through scans to get scan info
|
|
138
|
+
for i, spec in enumerate(data):
|
|
139
|
+
scan_dict["scan"][i] = spec.ID
|
|
140
|
+
scan_dict["ms_level"][i] = spec.ms_level
|
|
141
|
+
scan_dict["scan_precision"][i] = spec._measured_precision
|
|
142
|
+
scan_dict["tic"][i] = spec.TIC
|
|
143
|
+
if spec.selected_precursors:
|
|
144
|
+
scan_dict["precursor_mz"][i] = spec.selected_precursors[0].get(
|
|
145
|
+
"mz", None
|
|
146
|
+
)
|
|
147
|
+
if spec["negative scan"] is not None:
|
|
148
|
+
scan_dict["polarity"][i] = "negative"
|
|
149
|
+
if spec["positive scan"] is not None:
|
|
150
|
+
scan_dict["polarity"][i] = "positive"
|
|
151
|
+
if spec["negative scan"] is not None and spec["positive scan"] is not None:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
"Error: scan {0} has both negative and positive polarity".format(
|
|
154
|
+
spec.ID
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
scan_dict["scan_time"][i] = spec.get("MS:1000016")
|
|
159
|
+
scan_dict["scan_text"][i] = spec.get("MS:1000512")
|
|
160
|
+
scan_dict["scan_window_lower"][i] = spec.get("MS:1000501")
|
|
161
|
+
scan_dict["scan_window_upper"][i] = spec.get("MS:1000500")
|
|
162
|
+
if spec.get("MS:1000128"):
|
|
163
|
+
scan_dict["ms_format"][i] = "profile"
|
|
164
|
+
elif spec.get("MS:1000127"):
|
|
165
|
+
scan_dict["ms_format"][i] = "centroid"
|
|
166
|
+
else:
|
|
167
|
+
scan_dict["ms_format"][i] = None
|
|
168
|
+
|
|
169
|
+
scan_df = pd.DataFrame(scan_dict)
|
|
170
|
+
|
|
171
|
+
# Remove any non-mass spectra scans (e.g., MS level 0 or None)
|
|
172
|
+
scan_df = scan_df[scan_df.ms_level.notnull() & (scan_df.ms_level > 0)].reset_index(drop=True)
|
|
173
|
+
|
|
174
|
+
# Apply time range filtering if specified
|
|
175
|
+
if time_range is not None:
|
|
176
|
+
time_ranges = self._normalize_time_range(time_range)
|
|
177
|
+
# Create a mask for scans within any of the time ranges
|
|
178
|
+
mask = np.zeros(len(scan_df), dtype=bool)
|
|
179
|
+
for start_time, end_time in time_ranges:
|
|
180
|
+
mask |= (scan_df["scan_time"] >= start_time) & (scan_df["scan_time"] <= end_time)
|
|
181
|
+
scan_df = scan_df[mask].reset_index(drop=True)
|
|
182
|
+
|
|
183
|
+
return scan_df
|
|
184
|
+
|
|
185
|
+
def get_ms_raw(self, spectra, scan_df, data=None, time_range: Optional[Union[Tuple[float, float], List[Tuple[float, float]]]] = None):
|
|
186
|
+
"""Return a dictionary of mass spectra data as a pandas DataFrame.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
spectra : str
|
|
191
|
+
Which mass spectra data to include in the output.
|
|
192
|
+
Options: None, "ms1", "ms2", "all".
|
|
193
|
+
scan_df : pandas.DataFrame
|
|
194
|
+
Scan dataframe. Output from get_scan_df().
|
|
195
|
+
data : pymzml.run.Reader, optional
|
|
196
|
+
The mass spectra data. If None, will load the data.
|
|
197
|
+
time_range : tuple or list of tuples, optional
|
|
198
|
+
Retention time range(s) to filter scans. Can be:
|
|
199
|
+
- Single range: (start_time, end_time) in minutes
|
|
200
|
+
- Multiple ranges: [(start1, end1), (start2, end2), ...] in minutes
|
|
201
|
+
If None, returns all scans. Note: filtering is typically done at scan_df level.
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
dict
|
|
206
|
+
A dictionary containing the mass spectra data as pandas DataFrames, with keys corresponding to the MS level.
|
|
207
|
+
|
|
208
|
+
"""
|
|
209
|
+
if data is None:
|
|
210
|
+
data = self.load()
|
|
211
|
+
if spectra == "all":
|
|
212
|
+
scan_df_forspec = scan_df
|
|
213
|
+
elif spectra == "ms1":
|
|
214
|
+
scan_df_forspec = scan_df[scan_df.ms_level == 1]
|
|
215
|
+
elif spectra == "ms2":
|
|
216
|
+
scan_df_forspec = scan_df[scan_df.ms_level == 2]
|
|
217
|
+
else:
|
|
218
|
+
raise ValueError("spectra must be 'all', 'ms1', or 'ms2'")
|
|
219
|
+
|
|
220
|
+
# Result container
|
|
221
|
+
res = {}
|
|
222
|
+
|
|
223
|
+
# Row count container
|
|
224
|
+
counter = {}
|
|
225
|
+
|
|
226
|
+
# Column name container
|
|
227
|
+
cols = {}
|
|
228
|
+
|
|
229
|
+
# set at float32
|
|
230
|
+
dtype = np.float32
|
|
231
|
+
|
|
232
|
+
# First pass: get nrows
|
|
233
|
+
N = defaultdict(lambda: 0)
|
|
234
|
+
for i, spec in enumerate(data):
|
|
235
|
+
if spec.ID in scan_df_forspec.scan.values:
|
|
236
|
+
# Get ms level
|
|
237
|
+
level = "ms{}".format(spec.ms_level)
|
|
238
|
+
|
|
239
|
+
# Number of rows
|
|
240
|
+
N[level] += spec.mz.shape[0]
|
|
241
|
+
|
|
242
|
+
# Second pass: parse
|
|
243
|
+
for i, spec in enumerate(data):
|
|
244
|
+
if spec.ID in scan_df_forspec.scan.values:
|
|
245
|
+
# Number of rows
|
|
246
|
+
n = spec.mz.shape[0]
|
|
247
|
+
|
|
248
|
+
# No measurements
|
|
249
|
+
if n == 0:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
# Dimension check
|
|
253
|
+
if len(spec.mz) != len(spec.i):
|
|
254
|
+
# raise an error if the mz and intensity arrays are not the same length
|
|
255
|
+
raise ValueError("m/z and intensity array dimension mismatch")
|
|
256
|
+
|
|
257
|
+
# Scan/frame info
|
|
258
|
+
id_dict = spec.id_dict
|
|
259
|
+
|
|
260
|
+
# Get ms level
|
|
261
|
+
level = "ms{}".format(spec.ms_level)
|
|
262
|
+
|
|
263
|
+
# Columns
|
|
264
|
+
cols[level] = list(id_dict.keys()) + ["mz", "intensity"]
|
|
265
|
+
m = len(cols[level])
|
|
266
|
+
|
|
267
|
+
# Subarray init
|
|
268
|
+
arr = np.empty((n, m), dtype=dtype)
|
|
269
|
+
inx = 0
|
|
270
|
+
|
|
271
|
+
# Populate scan/frame info
|
|
272
|
+
for k, v in id_dict.items():
|
|
273
|
+
arr[:, inx] = v
|
|
274
|
+
inx += 1
|
|
275
|
+
|
|
276
|
+
# Populate m/z
|
|
277
|
+
arr[:, inx] = spec.mz
|
|
278
|
+
inx += 1
|
|
279
|
+
|
|
280
|
+
# Populate intensity
|
|
281
|
+
arr[:, inx] = spec.i
|
|
282
|
+
inx += 1
|
|
283
|
+
|
|
284
|
+
# Initialize output container
|
|
285
|
+
if level not in res:
|
|
286
|
+
res[level] = np.empty((N[level], m), dtype=dtype)
|
|
287
|
+
counter[level] = 0
|
|
288
|
+
|
|
289
|
+
# Insert subarray
|
|
290
|
+
res[level][counter[level] : counter[level] + n, :] = arr
|
|
291
|
+
counter[level] += n
|
|
292
|
+
|
|
293
|
+
# Construct ms1 and ms2 mz dataframes
|
|
294
|
+
for level in res.keys():
|
|
295
|
+
res[level] = pd.DataFrame(res[level], columns=cols[level]).drop(
|
|
296
|
+
columns=["controllerType", "controllerNumber"],
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return res
|
|
300
|
+
|
|
301
|
+
def run(self, spectra="all", scan_df=None, time_range: Optional[Union[Tuple[float, float], List[Tuple[float, float]]]] = None):
|
|
302
|
+
"""Parse the mzML file and return a dictionary of spectra dataframes and a scan metadata dataframe.
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
spectra : str, optional
|
|
307
|
+
Which mass spectra data to include in the output. Default is "all".
|
|
308
|
+
Other options: None, "ms1", "ms2".
|
|
309
|
+
scan_df : pandas.DataFrame, optional
|
|
310
|
+
Scan dataframe. If not provided, the scan dataframe is created from the mzML file.
|
|
311
|
+
time_range : tuple or list of tuples, optional
|
|
312
|
+
Retention time range(s) to load. Can be:
|
|
313
|
+
- Single range: (start_time, end_time) in minutes
|
|
314
|
+
- Multiple ranges: [(start1, end1), (start2, end2), ...] in minutes
|
|
315
|
+
If None, loads all scans.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
tuple
|
|
320
|
+
A tuple containing two elements:
|
|
321
|
+
- A dictionary containing the mass spectra data as numpy arrays, with keys corresponding to the MS level.
|
|
322
|
+
- A pandas DataFrame containing metadata for each scan, including scan number, MS level, polarity, and scan time.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
# Open file
|
|
326
|
+
data = self.load()
|
|
327
|
+
|
|
328
|
+
if scan_df is None:
|
|
329
|
+
scan_df = self.get_scan_df(data, time_range=time_range)
|
|
330
|
+
|
|
331
|
+
if spectra != "none":
|
|
332
|
+
res = self.get_ms_raw(spectra, scan_df, data)
|
|
333
|
+
|
|
334
|
+
else:
|
|
335
|
+
res = None
|
|
336
|
+
|
|
337
|
+
return res, scan_df
|
|
338
|
+
|
|
339
|
+
def get_mass_spectrum_from_scan(
|
|
340
|
+
self, scan_number, spectrum_mode, auto_process=True
|
|
341
|
+
):
|
|
342
|
+
"""Instatiate a mass spectrum object from the mzML file.
|
|
343
|
+
|
|
344
|
+
Parameters
|
|
345
|
+
----------
|
|
346
|
+
scan_number : int
|
|
347
|
+
The scan number to be parsed.
|
|
348
|
+
spectrum_mode : str
|
|
349
|
+
The type of spectrum to instantiate. Must be'profile' or 'centroid'.
|
|
350
|
+
polarity : int
|
|
351
|
+
The polarity of the scan. Must be -1 or 1.
|
|
352
|
+
auto_process : bool, optional
|
|
353
|
+
If True, process the mass spectrum. Default is True.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
MassSpecProfile | MassSpecCentroid
|
|
358
|
+
The MassSpecProfile or MassSpecCentroid object containing the parsed mass spectrum.
|
|
359
|
+
"""
|
|
360
|
+
# Use the batch function and return the first result
|
|
361
|
+
result_list = self.get_mass_spectra_from_scan_list(
|
|
362
|
+
[scan_number], spectrum_mode, auto_process
|
|
363
|
+
)
|
|
364
|
+
return result_list[0] if result_list else None
|
|
365
|
+
|
|
366
|
+
def get_mass_spectra_from_scan_list(
|
|
367
|
+
self, scan_list, spectrum_mode, auto_process=True
|
|
368
|
+
):
|
|
369
|
+
"""Instatiate mass spectrum objects from the mzML file.
|
|
370
|
+
|
|
371
|
+
Parameters
|
|
372
|
+
----------
|
|
373
|
+
scan_list : list of int
|
|
374
|
+
The scan numbers to be parsed.
|
|
375
|
+
spectrum_mode : str
|
|
376
|
+
The type of spectrum to instantiate. Must be'profile' or 'centroid'.
|
|
377
|
+
auto_process : bool, optional
|
|
378
|
+
If True, process the mass spectrum. Default is True.
|
|
379
|
+
|
|
380
|
+
Returns
|
|
381
|
+
-------
|
|
382
|
+
list of MassSpecProfile | MassSpecCentroid
|
|
383
|
+
List of MassSpecProfile or MassSpecCentroid objects containing the parsed mass spectra.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
def set_metadata(
|
|
387
|
+
scan_number: int,
|
|
388
|
+
polarity: int,
|
|
389
|
+
file_location: str,
|
|
390
|
+
label=Labels.thermo_profile,
|
|
391
|
+
):
|
|
392
|
+
"""
|
|
393
|
+
Set the output parameters for creating a MassSpecProfile or MassSpecCentroid object.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
scan_number : int
|
|
398
|
+
The scan number.
|
|
399
|
+
polarity : int
|
|
400
|
+
The polarity of the data.
|
|
401
|
+
file_location : str
|
|
402
|
+
The file location.
|
|
403
|
+
label : str, optional
|
|
404
|
+
The label for the mass spectrum. Default is Labels.thermo_profile.
|
|
405
|
+
|
|
406
|
+
Returns
|
|
407
|
+
-------
|
|
408
|
+
dict
|
|
409
|
+
The output parameters ready for creating a MassSpecProfile or MassSpecCentroid object.
|
|
410
|
+
"""
|
|
411
|
+
d_params = default_parameters(file_location)
|
|
412
|
+
d_params["label"] = label
|
|
413
|
+
d_params["polarity"] = polarity
|
|
414
|
+
d_params["filename_path"] = file_location
|
|
415
|
+
d_params["scan_number"] = scan_number
|
|
416
|
+
|
|
417
|
+
return d_params
|
|
418
|
+
|
|
419
|
+
# Open file
|
|
420
|
+
data = self.load()
|
|
421
|
+
|
|
422
|
+
mass_spectrum_objects = []
|
|
423
|
+
scan_set = set(scan_list)
|
|
424
|
+
spec_by_id = {}
|
|
425
|
+
|
|
426
|
+
# Iterate once through the file to collect all requested scans.
|
|
427
|
+
# Direct random-access via data[scan_number] uses pymzml's byte-offset
|
|
428
|
+
# index, which is unreliable on Windows (CRLF vs LF byte offsets).
|
|
429
|
+
for spec in data:
|
|
430
|
+
if spec.ID in scan_set:
|
|
431
|
+
spec_by_id[spec.ID] = spec
|
|
432
|
+
|
|
433
|
+
for scan_number in scan_list:
|
|
434
|
+
spec = spec_by_id.get(scan_number)
|
|
435
|
+
if spec is None:
|
|
436
|
+
raise ValueError(
|
|
437
|
+
"Scan number %d not found in mzML file" % scan_number
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
# Get polarity
|
|
441
|
+
if spec["negative scan"] is not None:
|
|
442
|
+
polarity = -1
|
|
443
|
+
elif spec["positive scan"] is not None:
|
|
444
|
+
polarity = 1
|
|
445
|
+
|
|
446
|
+
# Get mass spectrum
|
|
447
|
+
if spectrum_mode == "profile":
|
|
448
|
+
# Check if profile
|
|
449
|
+
if not spec.get("MS:1000128"):
|
|
450
|
+
raise ValueError("spectrum is not profile")
|
|
451
|
+
data_dict = {
|
|
452
|
+
Labels.mz: spec.mz,
|
|
453
|
+
Labels.abundance: spec.i,
|
|
454
|
+
}
|
|
455
|
+
d_params = set_metadata(
|
|
456
|
+
scan_number,
|
|
457
|
+
polarity,
|
|
458
|
+
self.file_location,
|
|
459
|
+
label=Labels.simulated_profile,
|
|
460
|
+
)
|
|
461
|
+
mass_spectrum_obj = MassSpecProfile(
|
|
462
|
+
data_dict, d_params, auto_process=auto_process
|
|
463
|
+
)
|
|
464
|
+
elif spectrum_mode == "centroid":
|
|
465
|
+
# Check if centroided
|
|
466
|
+
if not spec.get("MS:1000127"):
|
|
467
|
+
raise ValueError("spectrum is not centroided")
|
|
468
|
+
data_dict = {
|
|
469
|
+
Labels.mz: spec.mz,
|
|
470
|
+
Labels.abundance: spec.i,
|
|
471
|
+
Labels.rp: [np.nan] * len(spec.mz),
|
|
472
|
+
Labels.s2n: [np.nan] * len(spec.i),
|
|
473
|
+
}
|
|
474
|
+
d_params = set_metadata(
|
|
475
|
+
scan_number, polarity, self.file_location, label=Labels.corems_centroid
|
|
476
|
+
)
|
|
477
|
+
mass_spectrum_obj = MassSpecCentroid(
|
|
478
|
+
data_dict, d_params, auto_process=auto_process
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
mass_spectrum_objects.append(mass_spectrum_obj)
|
|
482
|
+
|
|
483
|
+
return mass_spectrum_objects
|
|
484
|
+
|
|
485
|
+
def get_mass_spectra_obj(self, time_range: Optional[Union[Tuple[float, float], List[Tuple[float, float]]]] = None):
|
|
486
|
+
"""Instatiate a MassSpectraBase object from the mzML file.
|
|
487
|
+
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
time_range : tuple or list of tuples, optional
|
|
491
|
+
Retention time range(s) to load. Can be:
|
|
492
|
+
- Single range: (start_time, end_time) in minutes
|
|
493
|
+
- Multiple ranges: [(start1, end1), (start2, end2), ...] in minutes
|
|
494
|
+
If None, loads all scans.
|
|
495
|
+
|
|
496
|
+
Returns
|
|
497
|
+
-------
|
|
498
|
+
MassSpectraBase
|
|
499
|
+
The MassSpectra object containing the parsed mass spectra.
|
|
500
|
+
The object is instatiated with the mzML file, analyzer, instrument, sample name, and scan dataframe.
|
|
501
|
+
"""
|
|
502
|
+
_, scan_df = self.run(spectra=False, time_range=time_range)
|
|
503
|
+
mass_spectra_obj = MassSpectraBase(
|
|
504
|
+
self.file_location,
|
|
505
|
+
self.analyzer,
|
|
506
|
+
self.instrument_label,
|
|
507
|
+
self.sample_name,
|
|
508
|
+
self,
|
|
509
|
+
)
|
|
510
|
+
scan_df = scan_df.set_index("scan", drop=False)
|
|
511
|
+
mass_spectra_obj.scan_df = scan_df
|
|
512
|
+
|
|
513
|
+
return mass_spectra_obj
|
|
514
|
+
|
|
515
|
+
def get_lcms_obj(self, spectra="all", time_range: Optional[Union[Tuple[float, float], List[Tuple[float, float]]]] = None):
|
|
516
|
+
"""Instatiates a LCMSBase object from the mzML file.
|
|
517
|
+
|
|
518
|
+
Parameters
|
|
519
|
+
----------
|
|
520
|
+
spectra : str, optional
|
|
521
|
+
Which mass spectra data to include in the output. Default is all. Other options: none, ms1, ms2.
|
|
522
|
+
time_range : tuple or list of tuples, optional
|
|
523
|
+
Retention time range(s) to load. Can be:
|
|
524
|
+
- Single range: (start_time, end_time) in minutes
|
|
525
|
+
- Multiple ranges: [(start1, end1), (start2, end2), ...] in minutes
|
|
526
|
+
If None, loads all scans. Useful for targeted workflows to improve performance.
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
LCMSBase
|
|
531
|
+
LCMS object containing mass spectra data.
|
|
532
|
+
The object is instatiated with the mzML file, analyzer, instrument, sample name, scan dataframe,
|
|
533
|
+
and mz dataframe(s), as well as lists of scan numbers, retention times, and TICs.
|
|
534
|
+
"""
|
|
535
|
+
_, scan_df = self.run(spectra="none", time_range=time_range) # first run it to just get scan info
|
|
536
|
+
if spectra != "none":
|
|
537
|
+
res, scan_df = self.run(
|
|
538
|
+
scan_df=scan_df, spectra=spectra, time_range=time_range
|
|
539
|
+
) # second run to parse data
|
|
540
|
+
lcms_obj = LCMSBase(
|
|
541
|
+
self.file_location,
|
|
542
|
+
self.analyzer,
|
|
543
|
+
self.instrument_label,
|
|
544
|
+
self.sample_name,
|
|
545
|
+
self,
|
|
546
|
+
)
|
|
547
|
+
if spectra != "none":
|
|
548
|
+
for key in res:
|
|
549
|
+
key_int = int(key.replace("ms", ""))
|
|
550
|
+
res[key] = res[key][res[key].intensity > 0]
|
|
551
|
+
res[key] = res[key].sort_values(by=["scan", "mz"]).reset_index(drop=True)
|
|
552
|
+
lcms_obj._ms_unprocessed[key_int] = res[key]
|
|
553
|
+
lcms_obj.scan_df = scan_df.set_index("scan", drop=False)
|
|
554
|
+
# Check if polarity is mixed
|
|
555
|
+
if len(set(scan_df.polarity)) > 1:
|
|
556
|
+
raise ValueError("Mixed polarities detected in scan data")
|
|
557
|
+
lcms_obj.polarity = scan_df.polarity[0]
|
|
558
|
+
lcms_obj._scans_number_list = list(scan_df.scan)
|
|
559
|
+
lcms_obj._retention_time_list = list(scan_df.scan_time)
|
|
560
|
+
lcms_obj._tic_list = list(scan_df.tic)
|
|
561
|
+
|
|
562
|
+
return lcms_obj
|
|
563
|
+
|
|
564
|
+
def get_scans_in_time_range(
|
|
565
|
+
self,
|
|
566
|
+
time_range: Union[Tuple[float, float], List[Tuple[float, float]]],
|
|
567
|
+
ms_level: Optional[int] = None
|
|
568
|
+
) -> List[int]:
|
|
569
|
+
"""
|
|
570
|
+
Return scan numbers within specified retention time range(s).
|
|
571
|
+
|
|
572
|
+
This method provides efficient filtering of scans by retention time,
|
|
573
|
+
which is particularly useful for targeted workflows where only specific
|
|
574
|
+
time windows are of interest.
|
|
575
|
+
|
|
576
|
+
Parameters
|
|
577
|
+
----------
|
|
578
|
+
time_range : tuple or list of tuples
|
|
579
|
+
Retention time range(s) in minutes. Can be:
|
|
580
|
+
- Single range: (start_time, end_time)
|
|
581
|
+
- Multiple ranges: [(start1, end1), (start2, end2), ...]
|
|
582
|
+
ms_level : int, optional
|
|
583
|
+
If specified, only return scans of this MS level (e.g., 1 for MS1, 2 for MS2).
|
|
584
|
+
If None, returns scans of all MS levels.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
list of int
|
|
589
|
+
List of scan numbers within the specified time range(s) and MS level.
|
|
590
|
+
|
|
591
|
+
Examples
|
|
592
|
+
--------
|
|
593
|
+
Get MS1 scans between 1.0 and 2.0 minutes:
|
|
594
|
+
|
|
595
|
+
>>> scans = parser.get_scans_in_time_range((1.0, 2.0), ms_level=1)
|
|
596
|
+
|
|
597
|
+
Get scans in multiple time windows:
|
|
598
|
+
|
|
599
|
+
>>> scans = parser.get_scans_in_time_range([(0.5, 1.5), (3.0, 4.0)])
|
|
600
|
+
"""
|
|
601
|
+
# Get scan dataframe filtered by time range
|
|
602
|
+
scan_df = self.get_scan_df(time_range=time_range)
|
|
603
|
+
|
|
604
|
+
# Further filter by MS level if specified
|
|
605
|
+
if ms_level is not None:
|
|
606
|
+
scan_df = scan_df[scan_df.ms_level == ms_level]
|
|
607
|
+
|
|
608
|
+
# Return list of scan numbers
|
|
609
|
+
return scan_df.scan.tolist()
|
|
610
|
+
|
|
611
|
+
def get_instrument_info(self):
|
|
612
|
+
"""
|
|
613
|
+
Return instrument information.
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
dict
|
|
618
|
+
A dictionary with the keys 'model' and 'serial_number'.
|
|
619
|
+
"""
|
|
620
|
+
# Load the pymzml data
|
|
621
|
+
data = self.load()
|
|
622
|
+
instrument_info = data.info.get('referenceable_param_group_list_element')[0]
|
|
623
|
+
cv_params = instrument_info.findall('{http://psi.hupo.org/ms/mzml}cvParam')
|
|
624
|
+
|
|
625
|
+
# Extract details from each cvParam
|
|
626
|
+
params = []
|
|
627
|
+
for param in cv_params:
|
|
628
|
+
accession = param.get('accession') # Get 'accession' attribute
|
|
629
|
+
name = param.get('name') # Get 'name' attribute
|
|
630
|
+
value = param.get('value') # Get 'value' attribute
|
|
631
|
+
params.append({
|
|
632
|
+
'accession': accession,
|
|
633
|
+
'name': name,
|
|
634
|
+
'value': value
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
# Loop through params and try to find the relevant information
|
|
638
|
+
instrument_dict = {
|
|
639
|
+
'model': 'Unknown',
|
|
640
|
+
'serial_number': 'Unknown'
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
# Assuming there are only two paramters here - one is for the serial number (agnostic to the model) and the other is for the model
|
|
644
|
+
# If there are more than two, we raise an error
|
|
645
|
+
if len(params) < 2:
|
|
646
|
+
raise ValueError("Not enough parameters found in the instrument info, cannot parse.")
|
|
647
|
+
if len(params) > 2:
|
|
648
|
+
raise ValueError("Too many parameters found in the instrument info, cannot parse.")
|
|
649
|
+
for param in params:
|
|
650
|
+
if param['accession'] == 'MS:1000529':
|
|
651
|
+
instrument_dict['serial_number'] = param['value']
|
|
652
|
+
else:
|
|
653
|
+
instrument_dict['model'] = data.OT[param['accession']]
|
|
654
|
+
|
|
655
|
+
return instrument_dict
|
|
656
|
+
|
|
657
|
+
def get_creation_time(self) -> datetime.datetime:
|
|
658
|
+
"""
|
|
659
|
+
Return the creation time of the mzML file.
|
|
660
|
+
"""
|
|
661
|
+
data = self.load()
|
|
662
|
+
write_time = data.info.get('start_time')
|
|
663
|
+
if write_time:
|
|
664
|
+
# Convert the write time to a datetime object
|
|
665
|
+
return datetime.datetime.strptime(write_time, "%Y-%m-%dT%H:%M:%SZ")
|
|
666
|
+
else:
|
|
667
|
+
raise ValueError("Creation time is not available in the mzML file. "
|
|
668
|
+
"Please ensure the file contains the 'start_time' information.")
|