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
corems/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
__author__ = "Yuri E. Corilo"
|
|
2
|
+
__version__ = "4.0.0"
|
|
3
|
+
import time
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import hashlib
|
|
7
|
+
|
|
8
|
+
# Get the path to the README file
|
|
9
|
+
readme_path = os.path.join(os.path.dirname(__file__), "..", "README.md")
|
|
10
|
+
|
|
11
|
+
# Read the contents of the README file if it exists
|
|
12
|
+
if os.path.exists(readme_path):
|
|
13
|
+
try:
|
|
14
|
+
with open(readme_path, "r", encoding="utf-8") as readme_file:
|
|
15
|
+
__doc__ = readme_file.read()
|
|
16
|
+
except Exception as e:
|
|
17
|
+
__doc__ = "CoreMS: A comprehensive mass spectrometry framework for software development and data analysis of small molecules analysis."
|
|
18
|
+
print(f"Warning: Could not read README.md file. Error: {e}")
|
|
19
|
+
else:
|
|
20
|
+
__doc__ = "CoreMS: A comprehensive mass spectrometry framework for software development and data analysis of small molecules analysis."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def timeit(print_time=True):
|
|
25
|
+
def decorator(method):
|
|
26
|
+
def timed(*args, **kw):
|
|
27
|
+
# Extract print_time from kwargs if provided
|
|
28
|
+
local_print_time = kw.pop('print_time', print_time)
|
|
29
|
+
ts = time.time()
|
|
30
|
+
result = method(*args, **kw)
|
|
31
|
+
te = time.time()
|
|
32
|
+
if "log_time" in kw:
|
|
33
|
+
name = kw.get("log_name", method.__name__.upper())
|
|
34
|
+
kw["log_time"][name] = int((te - ts) * 1000)
|
|
35
|
+
elif local_print_time:
|
|
36
|
+
print("%r %2.2f ms" % (method.__name__, (te - ts) * 1000))
|
|
37
|
+
return result
|
|
38
|
+
return timed
|
|
39
|
+
return decorator
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SuppressPrints:
|
|
43
|
+
def __enter__(self):
|
|
44
|
+
self._original_stdout = sys.stdout
|
|
45
|
+
sys.stdout = open(os.devnull, "w")
|
|
46
|
+
|
|
47
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
48
|
+
sys.stdout.close()
|
|
49
|
+
sys.stdout = self._original_stdout
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def chunks(lst, n):
|
|
53
|
+
"""Yield successive n-sized chunks from lst."""
|
|
54
|
+
for i in range(0, len(lst), n):
|
|
55
|
+
yield lst[i : i + n]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def corems_md5(fname):
|
|
59
|
+
bytes_io = fname.open("rb").read()
|
|
60
|
+
|
|
61
|
+
md5_returned = hashlib.sha256(bytes_io).hexdigest()
|
|
62
|
+
|
|
63
|
+
return "{}:{}".format("sha256", md5_returned)
|
|
File without changes
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from bisect import bisect_left
|
|
3
|
+
from scipy.optimize import curve_fit
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
np.trapezoid
|
|
7
|
+
except AttributeError: # numpy < 2.0
|
|
8
|
+
np.trapezoid = np.trapz
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__author__ = "Yuri E. Corilo"
|
|
12
|
+
__date__ = "March 11, 2020"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GCPeakCalculation(object):
|
|
16
|
+
"""
|
|
17
|
+
Class for performing peak calculations in GC chromatography.
|
|
18
|
+
|
|
19
|
+
Methods
|
|
20
|
+
-------
|
|
21
|
+
* `calc_area(self, tic: List[float], dx: float) -> None`: Calculate the area under the curve of the chromatogram.
|
|
22
|
+
* `linear_ri(self, right_ri: float, left_ri: float, left_rt: float, right_rt: float) -> float`: Calculate the retention index using linear interpolation.
|
|
23
|
+
* `calc_ri(self, rt_ri_pairs: List[Tuple[float, float]]) -> int`: Calculate the retention index based on the given retention time - retention index pairs.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def calc_area(self, tic: list[float], dx: float) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Calculate the area under the curve of the chromatogram.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
tic : List[float]
|
|
33
|
+
The total ion current (TIC) values.
|
|
34
|
+
dx : float
|
|
35
|
+
The spacing between data points.
|
|
36
|
+
"""
|
|
37
|
+
yy = tic[self.start_scan : self.final_scan]
|
|
38
|
+
self._area = np.trapezoid(yy, dx=dx)
|
|
39
|
+
|
|
40
|
+
def linear_ri(
|
|
41
|
+
self, right_ri: float, left_ri: float, left_rt: float, right_rt: float
|
|
42
|
+
) -> float:
|
|
43
|
+
"""
|
|
44
|
+
Calculate the retention index using linear interpolation.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
right_ri : float
|
|
49
|
+
The retention index at the right reference point.
|
|
50
|
+
left_ri : float
|
|
51
|
+
The retention index at the left reference point.
|
|
52
|
+
left_rt : float
|
|
53
|
+
The retention time at the left reference point.
|
|
54
|
+
right_rt : float
|
|
55
|
+
The retention time at the right reference point.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
float
|
|
60
|
+
The calculated retention index.
|
|
61
|
+
"""
|
|
62
|
+
return left_ri + (
|
|
63
|
+
(right_ri - left_ri)
|
|
64
|
+
* (self.retention_time - left_rt)
|
|
65
|
+
/ (right_rt - left_rt)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def calc_ri(self, rt_ri_pairs: list[tuple[float, float]]) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Calculate the retention index based on the given retention time - retention index pairs.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
rt_ri_pairs : List[Tuple[float, float]]
|
|
75
|
+
The list of retention time - retention index pairs.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
current_rt = self.retention_time
|
|
79
|
+
|
|
80
|
+
rts = [rt_ri[0] for rt_ri in rt_ri_pairs]
|
|
81
|
+
index = bisect_left(rts, current_rt)
|
|
82
|
+
|
|
83
|
+
if index >= len(rt_ri_pairs):
|
|
84
|
+
index -= 1
|
|
85
|
+
|
|
86
|
+
current_ref = rt_ri_pairs[index]
|
|
87
|
+
|
|
88
|
+
if current_rt == current_ref[0]:
|
|
89
|
+
self._ri = current_ref[1]
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
if index == 0:
|
|
93
|
+
index += 1
|
|
94
|
+
|
|
95
|
+
left_rt = rt_ri_pairs[index - 1][0]
|
|
96
|
+
left_ri = rt_ri_pairs[index - 1][1]
|
|
97
|
+
|
|
98
|
+
right_rt = rt_ri_pairs[index][0]
|
|
99
|
+
right_ri = rt_ri_pairs[index][1]
|
|
100
|
+
|
|
101
|
+
self._ri = self.linear_ri(right_ri, left_ri, left_rt, right_rt)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class LCMSMassFeatureCalculation:
|
|
105
|
+
"""Class for performing peak calculations in LC-MS mass spectrometry.
|
|
106
|
+
|
|
107
|
+
This class is intended to be used as a mixin class for the LCMSMassFeature class.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def calc_dispersity_index(self):
|
|
111
|
+
"""
|
|
112
|
+
Calculate the dispersity index of the mass feature.
|
|
113
|
+
|
|
114
|
+
This function calculates the dispersity index of the mass feature and
|
|
115
|
+
stores the result in the `_dispersity_index` attribute. The dispersity index is calculated as the standard
|
|
116
|
+
deviation of the retention times that account for 50% of the cummulative intensity, starting from the most
|
|
117
|
+
intense point, as described in [1]. Note that this calculation is done within the integration bounds with
|
|
118
|
+
a pad according to the window factor, where the window factor is parameterized and encapsulated in the
|
|
119
|
+
parent LCMS object (or, if not available, defaults to 2.0 minutes before and after the apex
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
None, stores the result in the `_dispersity_index` attribute of the class and the `_normalized_dispersity_index` attribute,
|
|
124
|
+
which is the dispersity index normalized to the total time window used for the calculation (unitless, fraction of total window).
|
|
125
|
+
|
|
126
|
+
Raises
|
|
127
|
+
------
|
|
128
|
+
ValueError
|
|
129
|
+
If the EIC data are not available.
|
|
130
|
+
|
|
131
|
+
References
|
|
132
|
+
----------
|
|
133
|
+
1) Boiteau, Rene M., et al. "Relating Molecular Properties to the Persistence of Marine Dissolved
|
|
134
|
+
Organic Matter with Liquid Chromatography–Ultrahigh-Resolution Mass Spectrometry."
|
|
135
|
+
Environmental Science & Technology 58.7 (2024): 3267-3277.
|
|
136
|
+
"""
|
|
137
|
+
# Check if LCMSMassFeature has a parent LCMS object with a window factor
|
|
138
|
+
if hasattr(self, "mass_spectrum_obj"):
|
|
139
|
+
window_min = self.mass_spectrum_obj.parameters.lc_ms.dispersity_index_window
|
|
140
|
+
else:
|
|
141
|
+
window_min = 3.0 # minutes
|
|
142
|
+
|
|
143
|
+
# Check if the EIC data is available
|
|
144
|
+
if self.eic_list is None:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"EIC data are not available. Please add the EIC data first."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Define start and end of the window around the apex
|
|
150
|
+
apex_rt = self.retention_time
|
|
151
|
+
full_time = self._eic_data.time
|
|
152
|
+
full_eic = self._eic_data.eic
|
|
153
|
+
left_start = apex_rt - window_min
|
|
154
|
+
right_end = apex_rt + window_min
|
|
155
|
+
|
|
156
|
+
# Extract the EIC data within the defined window
|
|
157
|
+
time_mask = (full_time >= left_start) & (full_time <= right_end)
|
|
158
|
+
eic_subset = full_eic[time_mask]
|
|
159
|
+
time_subset = full_time[time_mask]
|
|
160
|
+
|
|
161
|
+
# Sort the EIC data and RT data by descending intensity
|
|
162
|
+
sorted_eic = eic_subset[eic_subset.argsort()[::-1]]
|
|
163
|
+
sorted_rt = time_subset[eic_subset.argsort()[::-1]]
|
|
164
|
+
|
|
165
|
+
# Calculate the dispersity index
|
|
166
|
+
cum_sum = np.cumsum(sorted_eic) / np.sum(sorted_eic)
|
|
167
|
+
rt_summ = sorted_rt[np.where(cum_sum < 0.5)]
|
|
168
|
+
if len(rt_summ) > 1:
|
|
169
|
+
d = np.std(rt_summ)
|
|
170
|
+
self._dispersity_index = d # minutes
|
|
171
|
+
self._normalized_dispersity_index = d / (
|
|
172
|
+
time_subset[-1] - time_subset[0]
|
|
173
|
+
) # unitless (fraction of total window used)
|
|
174
|
+
elif len(rt_summ) == 1:
|
|
175
|
+
self._dispersity_index = 0
|
|
176
|
+
self._normalized_dispersity_index = 0
|
|
177
|
+
|
|
178
|
+
def calc_fraction_height_width(self, fraction: float):
|
|
179
|
+
"""
|
|
180
|
+
Calculate the height width of the mass feature at a specfic fraction of the maximum intensity.
|
|
181
|
+
|
|
182
|
+
This function returns a tuple with the minimum and maximum half-height width based on scan resolution.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
fraction : float
|
|
187
|
+
The fraction of the maximum intensity to calculate the height width.
|
|
188
|
+
For example, 0.5 will calculate the half-height width.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
Tuple[float, float, bool]
|
|
193
|
+
The minimum and maximum half-height width based on scan resolution (in minutes), and a boolean indicating if the width was estimated.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
# Pull out the EIC data
|
|
197
|
+
eic = self._eic_data.eic_smoothed
|
|
198
|
+
|
|
199
|
+
# Find the indices of the maximum intensity on either side
|
|
200
|
+
max_index = np.where(self._eic_data.scans == self.apex_scan)[0][0]
|
|
201
|
+
left_index = max_index
|
|
202
|
+
right_index = max_index
|
|
203
|
+
while eic[left_index] > eic[max_index] * fraction and left_index > 0:
|
|
204
|
+
left_index -= 1
|
|
205
|
+
while (
|
|
206
|
+
eic[right_index] > eic[max_index] * fraction and right_index < len(eic) - 1
|
|
207
|
+
):
|
|
208
|
+
right_index += 1
|
|
209
|
+
|
|
210
|
+
# Get the retention times of the indexes just below the half height
|
|
211
|
+
left_rt = self._eic_data.time[left_index]
|
|
212
|
+
right_rt = self._eic_data.time[right_index]
|
|
213
|
+
|
|
214
|
+
# If left_rt and right_rt are outside the bounds of the integration, set them to the bounds and set estimated to True
|
|
215
|
+
estimated = False
|
|
216
|
+
if left_rt < self.eic_rt_list[0]:
|
|
217
|
+
left_rt = self.eic_rt_list[0]
|
|
218
|
+
left_index = np.where(self._eic_data.scans == self._eic_data.apexes[0][0])[
|
|
219
|
+
0
|
|
220
|
+
][0]
|
|
221
|
+
estimated = True
|
|
222
|
+
if right_rt > self.eic_rt_list[-1]:
|
|
223
|
+
right_rt = self.eic_rt_list[-1]
|
|
224
|
+
right_index = np.where(
|
|
225
|
+
self._eic_data.scans == self._eic_data.apexes[0][-1]
|
|
226
|
+
)[0][0]
|
|
227
|
+
estimated = True
|
|
228
|
+
half_height_width_max = right_rt - left_rt
|
|
229
|
+
|
|
230
|
+
# Get the retention times of the indexes just above the half height
|
|
231
|
+
left_rt = self._eic_data.time[left_index + 1]
|
|
232
|
+
right_rt = self._eic_data.time[right_index - 1]
|
|
233
|
+
half_height_width_min = right_rt - left_rt
|
|
234
|
+
|
|
235
|
+
return half_height_width_min, half_height_width_max, estimated
|
|
236
|
+
|
|
237
|
+
def calc_half_height_width(self, accept_estimated: bool = False):
|
|
238
|
+
"""
|
|
239
|
+
Calculate the half-height width of the mass feature.
|
|
240
|
+
|
|
241
|
+
This function calculates the half-height width of the mass feature and
|
|
242
|
+
stores the result in the `_half_height_width` attribute
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
None, stores the result in the `_half_height_width` attribute of the class.
|
|
247
|
+
"""
|
|
248
|
+
min_, max_, estimated = self.calc_fraction_height_width(0.5)
|
|
249
|
+
if not estimated or accept_estimated:
|
|
250
|
+
self._half_height_width = np.array([min_, max_])
|
|
251
|
+
|
|
252
|
+
def calc_tailing_factor(self, accept_estimated: bool = False):
|
|
253
|
+
"""
|
|
254
|
+
Calculate the peak asymmetry of the mass feature.
|
|
255
|
+
|
|
256
|
+
This function calculates the peak asymmetry of the mass feature and
|
|
257
|
+
stores the result in the `_tailing_factor` attribute.
|
|
258
|
+
Calculations completed at 5% of the peak height in accordance with the USP tailing factor calculation.
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
None, stores the result in the `_tailing_factor` attribute of the class.
|
|
263
|
+
|
|
264
|
+
References
|
|
265
|
+
----------
|
|
266
|
+
1) JIS K0124:2011 General rules for high performance liquid chromatography
|
|
267
|
+
2) JIS K0214:2013 Technical terms for analytical chemistry
|
|
268
|
+
"""
|
|
269
|
+
# First calculate the width of the peak at 5% of the peak height
|
|
270
|
+
width_min, width_max, estimated = self.calc_fraction_height_width(0.05)
|
|
271
|
+
|
|
272
|
+
if not estimated or accept_estimated:
|
|
273
|
+
# Next calculate the width of the peak at 95% of the peak height
|
|
274
|
+
eic = self._eic_data.eic_smoothed
|
|
275
|
+
max_index = np.where(self._eic_data.scans == self.apex_scan)[0][0]
|
|
276
|
+
left_index = max_index
|
|
277
|
+
while eic[left_index] > eic[max_index] * 0.05 and left_index > 0:
|
|
278
|
+
left_index -= 1
|
|
279
|
+
|
|
280
|
+
left_half_time_min = (
|
|
281
|
+
self._eic_data.time[max_index] - self._eic_data.time[left_index]
|
|
282
|
+
)
|
|
283
|
+
left_half_time_max = (
|
|
284
|
+
self._eic_data.time[max_index] - self._eic_data.time[left_index + 1]
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
tailing_factor = np.mean([width_min, width_max]) / (
|
|
288
|
+
2 * np.mean([left_half_time_min, left_half_time_max])
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
self._tailing_factor = tailing_factor
|
|
292
|
+
|
|
293
|
+
def calc_gaussian_similarity(self):
|
|
294
|
+
"""
|
|
295
|
+
Calculate the Gaussian similarity score of the mass feature.
|
|
296
|
+
|
|
297
|
+
This function fits a Gaussian curve to the EIC data and evaluates
|
|
298
|
+
the goodness of fit using R-squared. Note that this only uses data within
|
|
299
|
+
the set integration bounds of the mass feature. A score close to 1 indicates
|
|
300
|
+
the peak closely resembles an ideal Gaussian shape.
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
None, stores the result in the `_gaussian_similarity` attribute of the class.
|
|
305
|
+
|
|
306
|
+
Raises
|
|
307
|
+
------
|
|
308
|
+
ValueError
|
|
309
|
+
If the EIC data are not available.
|
|
310
|
+
"""
|
|
311
|
+
# Check if the EIC data is available
|
|
312
|
+
if self.eic_list is None:
|
|
313
|
+
raise ValueError(
|
|
314
|
+
"EIC data are not available. Please add the EIC data first."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Get EIC data within integration bounds
|
|
318
|
+
time_data = np.array(self.eic_rt_list)
|
|
319
|
+
intensity_data = np.array(self.eic_list)
|
|
320
|
+
|
|
321
|
+
if len(time_data) < 4: # Need minimum points for meaningful fit
|
|
322
|
+
self._gaussian_similarity = np.nan
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# Check for valid intensity data
|
|
326
|
+
max_intensity = np.max(intensity_data)
|
|
327
|
+
if max_intensity == 0:
|
|
328
|
+
self._gaussian_similarity = np.nan
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
# Define Gaussian function
|
|
333
|
+
def gaussian(x, amplitude, mean, stddev, baseline):
|
|
334
|
+
return (
|
|
335
|
+
amplitude * np.exp(-((x - mean) ** 2) / (2 * stddev**2)) + baseline
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Initial parameter estimates
|
|
339
|
+
amplitude_init = max_intensity
|
|
340
|
+
mean_init = time_data[np.argmax(intensity_data)]
|
|
341
|
+
stddev_init = (time_data[-1] - time_data[0]) / 6 # Rough estimate
|
|
342
|
+
baseline_init = np.min(intensity_data)
|
|
343
|
+
|
|
344
|
+
# Fit Gaussian curve
|
|
345
|
+
popt, _ = curve_fit(
|
|
346
|
+
gaussian,
|
|
347
|
+
time_data,
|
|
348
|
+
intensity_data,
|
|
349
|
+
p0=[amplitude_init, mean_init, stddev_init, baseline_init],
|
|
350
|
+
maxfev=1000,
|
|
351
|
+
bounds=(
|
|
352
|
+
[0, time_data[0], 0, 0], # Lower bounds
|
|
353
|
+
[np.inf, time_data[-1], np.inf, max_intensity], # Upper bounds
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Calculate fitted values
|
|
358
|
+
fitted_intensities = gaussian(time_data, *popt)
|
|
359
|
+
|
|
360
|
+
# Calculate R-squared (coefficient of determination)
|
|
361
|
+
ss_res = np.sum((intensity_data - fitted_intensities) ** 2)
|
|
362
|
+
ss_tot = np.sum((intensity_data - np.mean(intensity_data)) ** 2)
|
|
363
|
+
|
|
364
|
+
if ss_tot == 0:
|
|
365
|
+
self._gaussian_similarity = np.nan
|
|
366
|
+
else:
|
|
367
|
+
r_squared = 1 - (ss_res / ss_tot)
|
|
368
|
+
# R² should be between 0 and 1 for reasonable fits
|
|
369
|
+
# If negative, the model is worse than the mean - treat as non-computable
|
|
370
|
+
self._gaussian_similarity = r_squared if r_squared >= 0 else np.nan
|
|
371
|
+
|
|
372
|
+
except (RuntimeError, ValueError, TypeError):
|
|
373
|
+
# Fitting failed, assign NaN
|
|
374
|
+
self._gaussian_similarity = np.nan
|
|
375
|
+
|
|
376
|
+
def calc_noise_score(self):
|
|
377
|
+
"""
|
|
378
|
+
Calculate the noise score of the mass feature separately for left and right sides.
|
|
379
|
+
|
|
380
|
+
This function estimates the signal-to-noise ratio by comparing the peak
|
|
381
|
+
intensity to the baseline noise level in surrounding regions. It calculates
|
|
382
|
+
separate scores for the left and right sides of the peak, which are stored as a tuple
|
|
383
|
+
in the `_noise_score` attribute. The noise estimation windows are encapsulated in the
|
|
384
|
+
parent LCMS object (or, if not available, defaults to twice the peak width on each side).
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
Returns
|
|
388
|
+
-------
|
|
389
|
+
None, stores the result in the `_noise_score` attribute as a tuple (left_score, right_score).
|
|
390
|
+
|
|
391
|
+
Raises
|
|
392
|
+
------
|
|
393
|
+
ValueError
|
|
394
|
+
If the EIC data are not available.
|
|
395
|
+
"""
|
|
396
|
+
# Check if the EIC data is available
|
|
397
|
+
if self.eic_list is None:
|
|
398
|
+
raise ValueError(
|
|
399
|
+
"EIC data are not available. Please add the EIC data first."
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Check if LCMSMassFeature has a parent LCMS object with a window factor
|
|
403
|
+
if hasattr(self, "mass_spectrum_obj"):
|
|
404
|
+
noise_window_factor = (
|
|
405
|
+
self.mass_spectrum_obj.parameters.lc_ms.noise_window_factor
|
|
406
|
+
)
|
|
407
|
+
else:
|
|
408
|
+
noise_window_factor = 2.0 # times the peak width
|
|
409
|
+
|
|
410
|
+
# Get full EIC data (not just integration bounds)
|
|
411
|
+
full_time = self._eic_data.time
|
|
412
|
+
full_eic = self._eic_data.eic
|
|
413
|
+
|
|
414
|
+
# Get peak information
|
|
415
|
+
apex_rt = self.retention_time
|
|
416
|
+
peak_intensity = np.max(self.eic_list)
|
|
417
|
+
|
|
418
|
+
# Retrieve width from integration bounds
|
|
419
|
+
peak_width = self.eic_rt_list[-1] - self.eic_rt_list[0]
|
|
420
|
+
|
|
421
|
+
# Define noise estimation windows
|
|
422
|
+
noise_window_size = peak_width * noise_window_factor # in minutes
|
|
423
|
+
left_noise_start = apex_rt - peak_width - noise_window_size
|
|
424
|
+
left_noise_end = apex_rt - peak_width
|
|
425
|
+
right_noise_start = apex_rt + peak_width
|
|
426
|
+
right_noise_end = apex_rt + peak_width + noise_window_size
|
|
427
|
+
|
|
428
|
+
# Extract noise regions
|
|
429
|
+
left_noise_mask = (full_time >= left_noise_start) & (
|
|
430
|
+
full_time <= left_noise_end
|
|
431
|
+
)
|
|
432
|
+
right_noise_mask = (full_time >= right_noise_start) & (
|
|
433
|
+
full_time <= right_noise_end
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
left_noise = full_eic[left_noise_mask]
|
|
437
|
+
right_noise = full_eic[right_noise_mask]
|
|
438
|
+
|
|
439
|
+
# Calculate left noise score
|
|
440
|
+
if len(left_noise) == 0:
|
|
441
|
+
left_score = np.nan
|
|
442
|
+
else:
|
|
443
|
+
left_baseline = np.median(left_noise)
|
|
444
|
+
left_noise_std = np.std(left_noise)
|
|
445
|
+
|
|
446
|
+
if left_noise_std == 0:
|
|
447
|
+
if peak_intensity > left_baseline:
|
|
448
|
+
left_score = 1.0
|
|
449
|
+
else:
|
|
450
|
+
left_score = np.nan
|
|
451
|
+
else:
|
|
452
|
+
left_signal = peak_intensity - left_baseline
|
|
453
|
+
if left_signal <= 0:
|
|
454
|
+
left_score = 0.0
|
|
455
|
+
else:
|
|
456
|
+
left_snr = left_signal / left_noise_std
|
|
457
|
+
left_score = min(1.0, left_snr / (left_snr + 10.0))
|
|
458
|
+
|
|
459
|
+
# Calculate right noise score
|
|
460
|
+
if len(right_noise) == 0:
|
|
461
|
+
right_score = np.nan
|
|
462
|
+
else:
|
|
463
|
+
right_baseline = np.median(right_noise)
|
|
464
|
+
right_noise_std = np.std(right_noise)
|
|
465
|
+
|
|
466
|
+
if right_noise_std == 0:
|
|
467
|
+
if peak_intensity > right_baseline:
|
|
468
|
+
right_score = 1.0
|
|
469
|
+
else:
|
|
470
|
+
right_score = np.nan
|
|
471
|
+
else:
|
|
472
|
+
right_signal = peak_intensity - right_baseline
|
|
473
|
+
if right_signal <= 0:
|
|
474
|
+
right_score = 0.0
|
|
475
|
+
else:
|
|
476
|
+
right_snr = right_signal / right_noise_std
|
|
477
|
+
right_score = min(1.0, right_snr / (right_snr + 10.0))
|
|
478
|
+
|
|
479
|
+
# Store as tuple
|
|
480
|
+
self._noise_score = (left_score, right_score)
|
|
File without changes
|