Skip to content

Implement iter_chromatograms: TIC is decoded-but-unused, BPC/SRM need small-to-larger new work #25

Description

@Nathan-D-R

Summary

openmassspec-core's SpectrumSource::iter_chromatograms has a default empty-iterator impl, so OpenTimsTDF's mzML output has no <chromatogramList> (the writer only emits it when a source yields something - openmassspec-core src/mzml.rs, landed in OpenMassSpecCore#1). OpenWRaw#9 wired an existing decoded-but-unused chroms.rs into iter_chromatograms for the same reason and is a good model for scope/format here.

TdfSource / OwnedTdfSource in crates/opentimstdf/src/mzml.rs (impls at lines 572 and 581) currently only implement iter_spectra. This issue is about implementing iter_chromatograms against openmassspec_core::types::ChromatogramRecord.

What's already decoded and unused (TIC - small)

Frame (crates/opentimstdf/src/types.rs lines 1-21) already carries:

pub time: f64,                          // retention time, seconds
pub summed_intensities: Option<u64>,    // normalized per-frame intensity sum

Reader::frames() (crates/opentimstdf/src/reader.rs lines 248-258) already runs:

SELECT Id, Time, NumScans, NumPeaks, TimsId, ScanMode, MsMsType,
       MzCalibration, AccumulationTime, SummedIntensities
FROM Frames ORDER BY Id ASC

and frame_from_row (crates/opentimstdf/src/codec.rs line 19) maps SummedIntensities into Frame.summed_intensities. Per docs/docs/format/01-tdf-sqlite-schema.md line 53, this column is sum(raw_intensity) * 100.0 / AccumulationTime_ms, verified against the decoded payload sum on PXD022216 (ratio 1.08460 +/- 0.00001 across 57,886 frames) - so it's a trustworthy, already-decoded TIC value per frame.

Searching the crate, summed_intensities is read nowhere except a test-mock Frame in mzml.rs (line 632, sets it to None). So (frame.time, frame.summed_intensities) across reader.frames() is a fully decoded TIC trace sitting unused - wiring this into one ChromatogramRecord (chromatogram_type = "total ion current chromatogram", time_sec/intensity built from existing Frame fields) is small, no new SQL or binary decode needed.

What needs a small net-new decode (BPC)

Frames.MaxIntensity is documented in the schema (docs/docs/format/01-tdf-sqlite-schema.md line 34, listed in the observed-columns block) but is not in either SELECT in reader.rs (lines 238-239 and 251-252) and there's no field for it on Frame in types.rs. Building a basepeak chromatogram needs: add MaxIntensity to both queries, add a max_intensity: Option<u64> field to Frame, map it in frame_from_row. Small, additive, no clean-room risk since the column and its role are already documented from public schema analysis.

What needs real net-new work (SRM/PRM traces)

PrmTargets and PrmFrameMsMsInfo already have per-frame/per-target lookups (Reader::prm_msms_info_for_frame, Reader::prm_target, reader.rs lines 357 and 395), but nothing aggregates a target's isolation-window intensity across frames into a time series. Producing an SRM-shaped ChromatogramRecord (chromatogram_type = "selected reaction monitoring chromatogram", precursor_mz/product_mz populated) per PRM target would mean, for each PrmTarget, walking msms_type == 10 frames, decoding the peak payload (reusing the existing per-frame decode path used for spectra) restricted to the target's scan-number range, and summing intensity per frame. That's genuinely new aggregation logic, not just wiring - larger than the TIC/BPC pieces. Related but distinct from #13 (PRM-PASEF frames decoded but skipped in the mzML spectrum projection): that issue is about spectra, this is about the chromatogram trace: fixing #13 does not fix this, and vice versa.

Suggested scope

Given the size difference, this issue is written broadly but a PR need not do all three at once - TIC is the cheapest, self-contained win (existing data, one new ChromatogramRecord), BPC is a small additive follow-on, SRM is optional/larger and could be split out if preferred.

Clean-room note

Per CONTRIBUTING.md, correctness here should be argued from self-consistency (e.g. TIC values matching Frames.SummedIntensities, roundtrip invariants, the PSI-MS mzML schema) - not by comparing output against vendor software or msconvert.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions