The calibration panel's Apply button writes a single RtTolerance::Minutes into the viewer's query tolerance. Phase 3 of a real search does not use a uniform window: CalibrationResult::get_tolerance (rust/timsseek/src/rt_calibration.rs:209-222) interpolates the measured ridge half-width at each query's own library RT (ridge_half_width_interp, :447-465) and falls back to a uniform scalar only where the ridge has no measurement there. Call site: rust/timsseek/src/scoring/pipeline.rs:570-576.
So a chromatogram the viewer extracts after clicking Apply is not the chromatogram the search extracts for that peptide.
There are two separate mismatches.
1. Uniform vs per-position. rust/timsquery_viewer/src/calibration.rs:1087-1090 applies one number to every peptide.
2. The scalar is the wrong scalar. The viewer's number is the Σ-weighted mean ridge half-width (calibration.rs:1033-1041, floored at 0.5 min at :1071). Production's fallback scalar is rt_sigma_factor * 1.4826 * MAD(rt residuals about the fitted curve) / 60, floored by min_rt_tolerance_minutes (rust/timsseek_cli/src/processing.rs:1046-1049; defaults 3.0 / 0.5 in rust/timsseek_cli/assets/default_config.toml:62-71). The weighted-mean ridge width is only a diagnostic in production (processing.rs:934-953, rt_calibration.rs:233-267) — calib_dash labels it as such (rust/calib_dash/src/app.rs, the ToleranceSummary::rt_seconds doc).
Reproduction
Load a raw file plus speclib, run calibration to Done, click Apply, then select any peptide whose ridge half-width differs from the weighted mean. The panel prints min/max, so any run where those differ by more than ~2× will do. The extracted chromatogram window equals ±weighted_mean, whereas a timsseek search of the same peptide extracts ±ridge_half_width_interp(library_rt).
Expected
The extraction window the viewer uses for a peptide equals the one Phase 3 would use for that peptide: the ridge half-width interpolated at that peptide's library RT, floored at MIN_RT_TOLERANCE_MINUTES, falling back to the MAD-derived scalar only where the ridge has no measurement.
Actual
One uniform window for every peptide, derived by a formula production uses only for reporting.
Notes for whoever picks this up
RtTolerance has no position-dependent variant (rust/timsquery/src/models/tolerance.rs:55-62), and data.tolerance is user-editable (rust/timsquery_viewer/src/ui/tolerance_editor.rs:74-123) and persisted (app.rs:334, 1403). The per-query window therefore has to be applied where the query is built (app.rs:527-545), not stored in the editor field. Suggested shape: keep Apply writing the (corrected) fallback scalar so the editor never lies, and add an explicit "use per-query ridge window" toggle.
- The chromatogram cache key is
(selected_idx, data.tolerance, smoothing) (computed_state.rs:246-258; callers app.rs:473, 583, 675, 726). It must see the effective tolerance, or a live re-fit that moves the ridge will serve a stale chromatogram.
MIN_RT_TOLERANCE_MINUTES (rt_calibration.rs:36) is private and already duplicated as a bare 0.5 at calibration.rs:1071. Make it pub rather than spelling it a third time.
calibration.rs has no tests today. Extracting an effective_tolerance(idx) helper out of generate_chromatogram is what makes this testable — the rest of that method is 130 lines of egui and thread spawning.
Estimated cost: ~76 lines across three files including the cache-key fix, plus ~45 lines of tests once the helper is extracted.
Found while auditing the new RT-calibration dashboard (calib_dash) against the viewer's analogous panel. Two related bugs in the same file are being fixed separately, since they are near-line-neutral and unit-testable: the grid axes being derived from the acquisition RT range rather than from the calibrant points, and the reload path weighing calibrants by score.
The calibration panel's Apply button writes a single
RtTolerance::Minutesinto the viewer's query tolerance. Phase 3 of a real search does not use a uniform window:CalibrationResult::get_tolerance(rust/timsseek/src/rt_calibration.rs:209-222) interpolates the measured ridge half-width at each query's own library RT (ridge_half_width_interp,:447-465) and falls back to a uniform scalar only where the ridge has no measurement there. Call site:rust/timsseek/src/scoring/pipeline.rs:570-576.So a chromatogram the viewer extracts after clicking Apply is not the chromatogram the search extracts for that peptide.
There are two separate mismatches.
1. Uniform vs per-position.
rust/timsquery_viewer/src/calibration.rs:1087-1090applies one number to every peptide.2. The scalar is the wrong scalar. The viewer's number is the Σ-weighted mean ridge half-width (
calibration.rs:1033-1041, floored at 0.5 min at:1071). Production's fallback scalar isrt_sigma_factor * 1.4826 * MAD(rt residuals about the fitted curve) / 60, floored bymin_rt_tolerance_minutes(rust/timsseek_cli/src/processing.rs:1046-1049; defaults 3.0 / 0.5 inrust/timsseek_cli/assets/default_config.toml:62-71). The weighted-mean ridge width is only a diagnostic in production (processing.rs:934-953,rt_calibration.rs:233-267) —calib_dashlabels it as such (rust/calib_dash/src/app.rs, theToleranceSummary::rt_secondsdoc).Reproduction
Load a raw file plus speclib, run calibration to Done, click Apply, then select any peptide whose ridge half-width differs from the weighted mean. The panel prints
min/max, so any run where those differ by more than ~2× will do. The extracted chromatogram window equals±weighted_mean, whereas atimsseeksearch of the same peptide extracts±ridge_half_width_interp(library_rt).Expected
The extraction window the viewer uses for a peptide equals the one Phase 3 would use for that peptide: the ridge half-width interpolated at that peptide's library RT, floored at
MIN_RT_TOLERANCE_MINUTES, falling back to the MAD-derived scalar only where the ridge has no measurement.Actual
One uniform window for every peptide, derived by a formula production uses only for reporting.
Notes for whoever picks this up
RtTolerancehas no position-dependent variant (rust/timsquery/src/models/tolerance.rs:55-62), anddata.toleranceis user-editable (rust/timsquery_viewer/src/ui/tolerance_editor.rs:74-123) and persisted (app.rs:334, 1403). The per-query window therefore has to be applied where the query is built (app.rs:527-545), not stored in the editor field. Suggested shape: keep Apply writing the (corrected) fallback scalar so the editor never lies, and add an explicit "use per-query ridge window" toggle.(selected_idx, data.tolerance, smoothing)(computed_state.rs:246-258; callersapp.rs:473, 583, 675, 726). It must see the effective tolerance, or a live re-fit that moves the ridge will serve a stale chromatogram.MIN_RT_TOLERANCE_MINUTES(rt_calibration.rs:36) is private and already duplicated as a bare0.5atcalibration.rs:1071. Make itpubrather than spelling it a third time.calibration.rshas no tests today. Extracting aneffective_tolerance(idx)helper out ofgenerate_chromatogramis what makes this testable — the rest of that method is 130 lines of egui and thread spawning.Estimated cost: ~76 lines across three files including the cache-key fix, plus ~45 lines of tests once the helper is extracted.
Found while auditing the new RT-calibration dashboard (
calib_dash) against the viewer's analogous panel. Two related bugs in the same file are being fixed separately, since they are near-line-neutral and unit-testable: the grid axes being derived from the acquisition RT range rather than from the calibrant points, and the reload path weighing calibrants by score.