Skip to content

Commit 503197e

Browse files
Add MSExperiment chromatogram integration with tests
Co-authored-by: timosachsenberg <[email protected]>
1 parent 40c8f31 commit 503197e

File tree

2 files changed

+213
-1
lines changed

2 files changed

+213
-1
lines changed

openms_python/py_msexperiment.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy as np
99
import pyopenms as oms
1010
from .py_msspectrum import Py_MSSpectrum
11+
from .py_chromatogram import Py_MSChromatogram
1112
from .py_featuremap import Py_FeatureMap
1213
from ._io_utils import ensure_allowed_suffix, MS_EXPERIMENT_EXTENSIONS
1314

@@ -266,6 +267,16 @@ def rt_filter(self) -> _Py_MSExperimentRTSlicing:
266267
"""
267268
return _Py_MSExperimentRTSlicing(self)
268269

270+
@property
271+
def nr_chromatograms(self) -> int:
272+
"""Get number of chromatograms in the experiment."""
273+
return self._experiment.getNrChromatograms()
274+
275+
@property
276+
def chromatogram_count(self) -> int:
277+
"""Alias for nr_chromatograms."""
278+
return self.nr_chromatograms
279+
269280
# ==================== Magic Methods ====================
270281

271282
def __len__(self) -> int:
@@ -281,8 +292,9 @@ def __repr__(self) -> str:
281292
"""Return string representation."""
282293
ms_levels_str = ', '.join(f"MS{level}" for level in sorted(self.ms_levels))
283294
rt_min, rt_max = self.rt_range
295+
chrom_str = f", chromatograms={self.nr_chromatograms}" if self.nr_chromatograms > 0 else ""
284296
return (
285-
f"MSExperiment(spectra={len(self)}, "
297+
f"MSExperiment(spectra={len(self)}{chrom_str}, "
286298
f"levels=[{ms_levels_str}], "
287299
f"rt_range=[{rt_min:.2f}, {rt_max:.2f}]s)"
288300
)
@@ -344,6 +356,52 @@ def spectra_in_rt_range(self, min_rt: float, max_rt: float) -> Iterator[Py_MSSpe
344356
if min_rt <= spec.retention_time <= max_rt:
345357
yield spec
346358

359+
# ==================== Chromatogram Access ====================
360+
361+
def get_chromatogram(self, index: int) -> Py_MSChromatogram:
362+
"""
363+
Get a chromatogram by index.
364+
365+
Args:
366+
index: Chromatogram index (0-based)
367+
368+
Returns:
369+
Py_MSChromatogram wrapper
370+
371+
Example:
372+
>>> chrom = exp.get_chromatogram(0)
373+
>>> print(f"MZ: {chrom.mz:.4f}, Points: {len(chrom)}")
374+
"""
375+
native_chrom = self._experiment.getChromatogram(index)
376+
return Py_MSChromatogram(native_chrom)
377+
378+
def chromatograms(self) -> Iterator[Py_MSChromatogram]:
379+
"""
380+
Iterate over all chromatograms.
381+
382+
Example:
383+
>>> for chrom in exp.chromatograms():
384+
... print(f"MZ: {chrom.mz:.4f}, TIC: {chrom.total_ion_current:.2e}")
385+
"""
386+
for i in range(self.nr_chromatograms):
387+
yield self.get_chromatogram(i)
388+
389+
def add_chromatogram(self, chromatogram: Union[Py_MSChromatogram, oms.MSChromatogram]):
390+
"""
391+
Add a chromatogram to the experiment.
392+
393+
Args:
394+
chromatogram: Chromatogram to add (Py_MSChromatogram or native)
395+
396+
Example:
397+
>>> chrom = Py_MSChromatogram.from_dataframe(df, mz=445.12)
398+
>>> exp.add_chromatogram(chrom)
399+
"""
400+
if isinstance(chromatogram, Py_MSChromatogram):
401+
self._experiment.addChromatogram(chromatogram.native)
402+
else:
403+
self._experiment.addChromatogram(chromatogram)
404+
347405
# ==================== DataFrame Conversion ====================
348406

349407
def to_dataframe(self, include_peaks: bool = True, ms_level: Optional[int] = None) -> pd.DataFrame:
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"""
2+
Tests for MSExperiment chromatogram integration.
3+
"""
4+
5+
import pytest
6+
import numpy as np
7+
import pandas as pd
8+
import pyopenms as oms
9+
10+
from openms_python import Py_MSExperiment, Py_MSChromatogram
11+
12+
13+
def test_msexperiment_chromatogram_count():
14+
"""Test chromatogram count properties."""
15+
exp = Py_MSExperiment()
16+
17+
# Initially empty
18+
assert exp.nr_chromatograms == 0
19+
assert exp.chromatogram_count == 0
20+
21+
# Add a chromatogram
22+
chrom = Py_MSChromatogram(oms.MSChromatogram())
23+
exp.add_chromatogram(chrom)
24+
25+
assert exp.nr_chromatograms == 1
26+
assert exp.chromatogram_count == 1
27+
28+
29+
def test_msexperiment_add_chromatogram():
30+
"""Test adding chromatograms to experiment."""
31+
exp = Py_MSExperiment()
32+
33+
# Create chromatogram from DataFrame
34+
df = pd.DataFrame({
35+
'rt': [10.0, 20.0, 30.0],
36+
'intensity': [100.0, 200.0, 150.0]
37+
})
38+
chrom = Py_MSChromatogram.from_dataframe(df, mz=445.12, name="XIC 445.12")
39+
40+
# Add it
41+
exp.add_chromatogram(chrom)
42+
43+
assert exp.nr_chromatograms == 1
44+
45+
# Add another
46+
chrom2 = Py_MSChromatogram.from_dataframe(df, mz=500.0, name="XIC 500.0")
47+
exp.add_chromatogram(chrom2)
48+
49+
assert exp.nr_chromatograms == 2
50+
51+
52+
def test_msexperiment_get_chromatogram():
53+
"""Test getting chromatograms by index."""
54+
exp = Py_MSExperiment()
55+
56+
# Add chromatograms
57+
df1 = pd.DataFrame({'rt': [10.0, 20.0], 'intensity': [100.0, 200.0]})
58+
chrom1 = Py_MSChromatogram.from_dataframe(df1, mz=445.12, name="First")
59+
exp.add_chromatogram(chrom1)
60+
61+
df2 = pd.DataFrame({'rt': [30.0, 40.0], 'intensity': [150.0, 250.0]})
62+
chrom2 = Py_MSChromatogram.from_dataframe(df2, mz=500.0, name="Second")
63+
exp.add_chromatogram(chrom2)
64+
65+
# Get them back
66+
retrieved1 = exp.get_chromatogram(0)
67+
assert retrieved1.mz == pytest.approx(445.12)
68+
assert retrieved1.name == "First"
69+
assert len(retrieved1) == 2
70+
71+
retrieved2 = exp.get_chromatogram(1)
72+
assert retrieved2.mz == pytest.approx(500.0)
73+
assert retrieved2.name == "Second"
74+
assert len(retrieved2) == 2
75+
76+
77+
def test_msexperiment_chromatograms_iteration():
78+
"""Test iterating over chromatograms."""
79+
exp = Py_MSExperiment()
80+
81+
# Add multiple chromatograms
82+
mz_values = [445.12, 500.0, 550.25]
83+
for mz in mz_values:
84+
df = pd.DataFrame({'rt': [10.0, 20.0, 30.0], 'intensity': [100.0, 200.0, 150.0]})
85+
chrom = Py_MSChromatogram.from_dataframe(df, mz=mz)
86+
exp.add_chromatogram(chrom)
87+
88+
# Iterate and check
89+
retrieved_mzs = []
90+
for chrom in exp.chromatograms():
91+
assert isinstance(chrom, Py_MSChromatogram)
92+
assert len(chrom) == 3
93+
retrieved_mzs.append(chrom.mz)
94+
95+
assert len(retrieved_mzs) == 3
96+
np.testing.assert_array_almost_equal(retrieved_mzs, mz_values)
97+
98+
99+
def test_msexperiment_repr_with_chromatograms():
100+
"""Test string representation includes chromatogram count."""
101+
exp = Py_MSExperiment()
102+
103+
# Add a spectrum
104+
spec = oms.MSSpectrum()
105+
spec.setRT(10.0)
106+
spec.setMSLevel(1)
107+
spec.set_peaks(([100.0, 200.0], [50.0, 100.0]))
108+
exp._experiment.addSpectrum(spec)
109+
110+
# Initially no chromatograms
111+
repr_str = repr(exp)
112+
assert "chromatograms" not in repr_str
113+
114+
# Add a chromatogram
115+
df = pd.DataFrame({'rt': [10.0, 20.0], 'intensity': [100.0, 200.0]})
116+
chrom = Py_MSChromatogram.from_dataframe(df, mz=445.12)
117+
exp.add_chromatogram(chrom)
118+
119+
repr_str = repr(exp)
120+
assert "chromatograms=1" in repr_str
121+
122+
123+
def test_msexperiment_add_native_chromatogram():
124+
"""Test adding native pyOpenMS chromatogram."""
125+
exp = Py_MSExperiment()
126+
127+
# Create native chromatogram
128+
native_chrom = oms.MSChromatogram()
129+
native_chrom.set_peaks(([10.0, 20.0, 30.0], [100.0, 200.0, 150.0]))
130+
product = native_chrom.getProduct()
131+
product.setMZ(445.12)
132+
native_chrom.setProduct(product)
133+
134+
# Add it directly
135+
exp.add_chromatogram(native_chrom)
136+
137+
assert exp.nr_chromatograms == 1
138+
139+
# Retrieve and verify
140+
retrieved = exp.get_chromatogram(0)
141+
assert retrieved.mz == pytest.approx(445.12)
142+
assert len(retrieved) == 3
143+
144+
145+
def test_msexperiment_empty_chromatograms():
146+
"""Test experiment with no chromatograms."""
147+
exp = Py_MSExperiment()
148+
149+
# Should not raise error
150+
assert exp.nr_chromatograms == 0
151+
152+
# Should return empty iterator
153+
chroms = list(exp.chromatograms())
154+
assert len(chroms) == 0

0 commit comments

Comments
 (0)