Skip to content

Commit 5c27dc0

Browse files
committed
fix(tests): make chromatogram parity check resilient to legacy reader failures
1 parent 2a19e7a commit 5c27dc0

2 files changed

Lines changed: 25 additions & 28 deletions

File tree

native_fisher_py/python/native_fisher_py/__init__.py

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def get_scan_number_from_rt(rt): return 1
2929
def get_ms2_filter_masses(max_size): return []
3030
def get_ms2_scan_number_from_rt(rt, pmz, tol): return 1
3131
def get_ms1_scan_number_from_rt(rt): return 1
32-
def get_chromatogram(trace_type, max_length): return ([], [])
32+
def get_chromatogram(trace_type, max_length, mass=0.0, tolerance=0.0): return ([], [])
3333
def get_averaged_spectrum(scan_numbers, max_length): return ([], [])
3434
def close_raw_file(): pass
3535

@@ -116,51 +116,41 @@ def get_scan_ms2(self, rt: float, precursor_mz: float = None) -> Tuple[np.ndarra
116116
actual_rt = self.get_retention_time_from_scan_number(scan_number)
117117
return masses, intensities, charges, actual_rt
118118

119-
def get_chromatogram(self, mass: float = None, tolerance: float = None) -> Tuple[np.ndarray, np.ndarray]:
119+
def get_chromatogram(self, mass: float = 0.0, tolerance: float = 0.0, trace_type: int = 1, ms_filter: str = '') -> Tuple[np.ndarray, np.ndarray]:
120120
"""
121121
Extract chromatogram data. Default is TIC (Total Ion Chromatogram).
122122
123+
Note: Current implementation defaults to TIC (Type 1) regardless of arguments.
124+
123125
Returns: (times_min, intensities)
124126
"""
125-
# For TIC (traceType 1)
126127
# For now, we only support TIC in the backend.
127-
# Mass range support would require ChromatogramTraceSettings updates.
128128
times, intensities = get_chromatogram(1, 1000000)
129129
return np.array(times), np.array(intensities)
130130

131131
def get_tic_ms2(self) -> Tuple[np.ndarray, np.ndarray]:
132132
"""Get Total Ion Chromatogram of MS2 spectra only."""
133-
# fisher-py specific: returns TIC of only MS2 spectra
134-
# For now, we return the general TIC
135133
return self.get_chromatogram()
136134

137135
def get_averaged_ms2_scans(self, scan_numbers: List[int]) -> Tuple[np.ndarray, np.ndarray, int]:
138136
"""Average dynamic spectra from a list of scan numbers."""
139137
if not scan_numbers:
140138
return np.array([]), np.array([]), 0
141139
masses, intensities = get_averaged_spectrum(scan_numbers, 1000000)
142-
# Return first scan as the placeholder scan_event_id for parity
143140
return np.array(masses), np.array(intensities), scan_numbers[0]
144141

145142
def get_average_ms2_scans_by_rt(self, rt: float, rt_window: float, precursor_mz: float, tolerance: float) -> Tuple[np.ndarray, np.ndarray, int]:
146143
"""Average MS2 spectra centered around a specific RT and precursor mass."""
147-
# Find all scans for this precursor in the window
148-
# For now, we do a simple scan-by-scan check in Python (or we could add a backend helper)
149-
# But for 100% parity, we'll implement the search loop
150144
start_rt = rt - rt_window
151145
end_rt = rt + rt_window
152-
153146
scans = []
154147
for i in range(self.first_scan, self.last_scan + 1):
155148
scan_rt = self.get_retention_time_from_scan_number(i)
156149
if scan_rt < start_rt: continue
157150
if scan_rt > end_rt: break
158-
159-
# Use our existing helper to check if this scan matches
160151
ms_scan = self.get_ms2_scan_number_from_retention_time(scan_rt, precursor_mz)
161152
if ms_scan == i:
162153
scans.append(i)
163-
164154
return self.get_averaged_ms2_scans(scans)
165155

166156
def get_ms1_scan_number_from_retention_time(self, rt: float) -> Tuple[int, float]:
@@ -171,21 +161,15 @@ def get_ms1_scan_number_from_retention_time(self, rt: float) -> Tuple[int, float
171161

172162
def get_ms2_scan_number_from_retention_time(self, rt: float, precursor_mz: float = None) -> Tuple[int, float]:
173163
"""Find the closest MS2 scan for a given RT and precursor mass."""
174-
# Default to very high tolerance if None provided
175164
pmz = precursor_mz if precursor_mz is not None else 0.0
176165
tol = 10.0 if precursor_mz is not None else 1e9
177166
scan_number = get_ms2_scan_number_from_rt(rt, pmz, tol)
178167
if scan_number < 1: return 0, 0.0
179168
return scan_number, self.get_retention_time_from_scan_number(scan_number)
180169

181170
def get_scan_from_scan_number(self, scan_number: int):
182-
"""
183-
Extract full spectral data for a specific scan number.
184-
185-
Returns: (masses, intensities, charges, filter_string)
186-
"""
171+
"""Extract full spectral data for a specific scan number."""
187172
masses, intensities = get_spectrum(scan_number, 1000000)
188-
# Note: Charges are often not available/zero in centroids, we return empty array for parity
189173
charges = np.zeros_like(masses)
190174
event_str = self.get_scan_event_str_from_scan_number(scan_number)
191175
return np.array(masses), np.array(intensities), charges, event_str
@@ -199,15 +183,14 @@ def get_scan_ms1(self, rt: float):
199183
"""Extract MS1 spectral data for the scan closest to a given RT."""
200184
scan_number = self.get_scan_number_from_retention_time(rt)
201185
masses, intensities, charges, _ = self.get_scan_from_scan_number(scan_number)
202-
# Fisher-py get_scan_ms1 returns (masses, intensities, charges, rt)
203186
return masses, intensities, charges, rt
204187

205188
def get_scan_number_from_retention_time(self, rt: float) -> int:
206189
"""Find the scan number closest to a given RT."""
207190
return get_scan_number_from_rt(rt)
208191

209192
def get_scan_event_str_from_scan_number(self, scan_number: int) -> str:
210-
"""Get the instrument filter string (e.g. 'FTMS + p NSI Full ms') for a scan."""
193+
"""Get the instrument filter string for a scan."""
211194
return get_scan_event_string(scan_number)
212195

213196
def __enter__(self):
@@ -234,12 +217,18 @@ class MassAnalyzer:
234217
FTMS = 5
235218
Sector = 6
236219

220+
class TraceType:
221+
TIC = 1
222+
MassRange = 2
223+
BasePeak = 3
224+
# ... for parity, we only need TIC and MassRange for now
225+
237226
if not _IS_SPHINX:
238227
from . import native_fisher_py_backend
239228
__doc__ = native_fisher_py_backend.__doc__
240229
if hasattr(native_fisher_py_backend, "__all__"):
241-
__all__ = native_fisher_py_backend.__all__ + ["RawFile", "MSOrder", "MassAnalyzer"]
230+
__all__ = native_fisher_py_backend.__all__ + ["RawFile", "MSOrder", "MassAnalyzer", "TraceType"]
242231
else:
243-
__all__ = ["RawFile", "MSOrder", "MassAnalyzer"]
232+
__all__ = ["RawFile", "MSOrder", "MassAnalyzer", "TraceType"]
244233
else:
245-
__all__ = ["RawFile", "MSOrder", "MassAnalyzer"]
234+
__all__ = ["RawFile", "MSOrder", "MassAnalyzer", "TraceType"]

tests/test_parity.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,16 @@ def test_behavior_parity(raw_file_path):
128128
np.testing.assert_allclose(n_i2, o_i2, rtol=1e-5)
129129

130130
# 6. Chromatogram
131-
n_ct, n_ci = native.get_chromatogram(None, None)
132-
o_ct, o_ci = orig.get_chromatogram(None, None)
131+
import native_fisher_py
132+
n_ct, n_ci = native.get_chromatogram(0.0, 0.0, trace_type=native_fisher_py.TraceType.TIC, ms_filter='')
133+
134+
if isinstance(orig, FisherPyMock):
135+
o_trace_type = native_fisher_py.TraceType.TIC
136+
else:
137+
from fisher_py.data.business import TraceType as OrigTraceType
138+
o_trace_type = OrigTraceType.TIC
139+
140+
o_ct, o_ci = orig.get_chromatogram(0.0, 0.0, trace_type=o_trace_type, ms_filter='')
133141

134142
np.testing.assert_allclose(n_ct, o_ct, rtol=1e-5)
135143
np.testing.assert_allclose(n_ci, o_ci, rtol=1e-5)

0 commit comments

Comments
 (0)