Skip to main content

timsrust_tdf/
quad_settings_reader.rs

1use std::sync::Arc;
2
3use timsrust_core::utils::vec::argsort;
4use timsrust_core::{
5    Im, InvertibleConverter, IsolationWindow, Mz, QuadrupoleSettings, ScanIndex,
6};
7
8use super::{
9    TDFPathLike,
10    file_readers::sql_reader::{
11        ReadableSqlTable, SqlReader, SqlReaderError,
12        frame_groups::SqlWindowGroup, quad_settings::SqlQuadSettings,
13    },
14};
15
16pub struct QuadrupoleSettingsReader {
17    quadrupole_settings: Vec<QuadrupoleSettings>,
18    sql_quadrupole_settings: Vec<SqlQuadSettings>,
19}
20
21impl QuadrupoleSettingsReader {
22    // TODO: refactor due to large size
23    #[allow(clippy::new_ret_no_self)]
24    pub fn new(
25        path: impl TDFPathLike,
26    ) -> Result<Vec<QuadrupoleSettings>, QuadrupoleSettingsReaderError> {
27        let tdf_sql_reader = SqlReader::open(path)?;
28        Self::from_sql_settings(&tdf_sql_reader)
29    }
30
31    fn from_sql_settings(
32        tdf_sql_reader: &SqlReader,
33    ) -> Result<Vec<QuadrupoleSettings>, QuadrupoleSettingsReaderError> {
34        let sql_quadrupole_settings =
35            SqlQuadSettings::from_sql_reader(tdf_sql_reader)?;
36        let window_group_count = sql_quadrupole_settings
37            .iter()
38            .map(|x| x.window_group)
39            .max()
40            .expect("SqlReader cannot return empty vecs, so there is always a max window_group");
41        let quadrupole_settings = (0..window_group_count)
42            .map(|window_group| QuadrupoleSettings {
43                index: window_group + 1,
44                ..Default::default()
45            })
46            .collect();
47        let mut quad_reader = Self {
48            quadrupole_settings,
49            sql_quadrupole_settings,
50        };
51        quad_reader.update_from_sql_quadrupole_settings();
52        quad_reader.resort_groups();
53        Ok(quad_reader.quadrupole_settings)
54    }
55
56    pub(crate) fn from_splitting<ImC: InvertibleConverter<ScanIndex, Im>>(
57        tdf_sql_reader: &SqlReader,
58        splitting_strat: FrameWindowSplittingStrategy<ImC>,
59    ) -> Result<Vec<QuadrupoleSettings>, QuadrupoleSettingsReaderError> {
60        let quadrupole_settings = Self::from_sql_settings(tdf_sql_reader)?;
61        let window_groups = SqlWindowGroup::from_sql_reader(tdf_sql_reader)?;
62        let expanded_quadrupole_settings = match splitting_strat {
63            FrameWindowSplittingStrategy::Quadrupole(x) => {
64                expand_quadrupole_settings(
65                    &window_groups,
66                    &quadrupole_settings,
67                    &x,
68                )
69            },
70            FrameWindowSplittingStrategy::Window(x) => {
71                expand_window_settings(&window_groups, &quadrupole_settings, &x)
72            },
73        };
74        Ok(expanded_quadrupole_settings)
75    }
76
77    fn update_from_sql_quadrupole_settings(&mut self) {
78        for window_group in self.sql_quadrupole_settings.iter() {
79            let group = window_group.window_group - 1;
80            self.quadrupole_settings[group]
81                .scan_starts
82                .push(window_group.scan_start);
83            self.quadrupole_settings[group]
84                .scan_ends
85                .push(window_group.scan_end);
86            let isolation_window = IsolationWindow::new_from_center(
87                Mz::from(window_group.mz_center),
88                Mz::from(window_group.mz_width),
89                window_group.collision_energy,
90            );
91            self.quadrupole_settings[group]
92                .isolation_windows
93                .push(isolation_window);
94        }
95    }
96
97    fn resort_groups(&mut self) {
98        self.quadrupole_settings = self
99            .quadrupole_settings
100            .iter()
101            .map(|_window| {
102                let mut window = _window.clone();
103                let order = argsort(&window.scan_starts);
104                window.isolation_windows = order
105                    .iter()
106                    .map(|&i| window.isolation_windows[i].clone())
107                    .collect();
108                window.scan_starts =
109                    order.iter().map(|&i| window.scan_starts[i]).collect();
110                window.scan_ends =
111                    order.iter().map(|&i| window.scan_ends[i]).collect();
112                window
113            })
114            .collect();
115    }
116}
117
118#[allow(private_interfaces)]
119#[derive(Debug, thiserror::Error)]
120pub enum QuadrupoleSettingsReaderError {
121    #[error("{0}")]
122    SqlReaderError(#[from] SqlReaderError),
123}
124
125type MobilitySpanStep = (f64, f64);
126type ScanSpanStep = (usize, usize);
127
128/// Strategy for expanding quadrupole settings
129///
130/// This enum is used to determine how to expand quadrupole settings
131/// when reading in DIA data. And exporting spectra (not frames RN).
132///
133/// # Variants
134///
135/// For example if we have a window with scan start 50 and end 500
136///
137/// * `None` - Do not expand quadrupole settings; use the original settings
138/// * `Even(usize)` - Split the quadrupole settings into `usize` evenly spaced
139///   subwindows; e.g. if `usize` is 2, the window will be split into 2 subwindows
140///   of equal width.
141/// * `UniformMobility(SpanStep)` - Split the quadrupole settings into subwindows of
142///   width `SpanStep.0` and step `SpanStep.1` in ion mobility space.
143///   e.g. if `SpanStep` is (0.05, 0.02),
144///   the window will be split into subwindows of width 0.05 and step 0.02 between their
145///   in the mobility dimension.
146/// * `UniformScan(SpanStep)` - Split the quadrupole settings into subwindows of
147///   width `SpanStep.0` and step `SpanStep.1` in scan number space.
148///   e.g. if `SpanStep` is (100, 80),
149///   the window will be split into subwindows of width
150///   100 and step 80 between their in the scan number.
151///
152#[derive(Debug)]
153pub enum QuadWindowExpansionStrategy<ImC> {
154    None,
155    Even(usize),
156    UniformMobility(MobilitySpanStep, Option<Arc<ImC>>),
157    UniformScan(ScanSpanStep),
158}
159
160impl<ImC> Clone for QuadWindowExpansionStrategy<ImC> {
161    fn clone(&self) -> Self {
162        match self {
163            Self::None => Self::None,
164            Self::Even(n) => Self::Even(*n),
165            Self::UniformMobility(span_step, converter) => {
166                Self::UniformMobility(*span_step, converter.clone())
167            },
168            Self::UniformScan(span_step) => Self::UniformScan(*span_step),
169        }
170    }
171}
172
173impl<ImC> Default for QuadWindowExpansionStrategy<ImC> {
174    fn default() -> Self {
175        Self::Even(1)
176    }
177}
178
179#[derive(Debug)]
180pub(crate) enum FrameWindowSplittingStrategy<ImC> {
181    Quadrupole(QuadWindowExpansionStrategy<ImC>),
182    Window(QuadWindowExpansionStrategy<ImC>),
183}
184
185impl<ImC> Clone for FrameWindowSplittingStrategy<ImC> {
186    fn clone(&self) -> Self {
187        match self {
188            Self::Quadrupole(s) => Self::Quadrupole(s.clone()),
189            Self::Window(s) => Self::Window(s.clone()),
190        }
191    }
192}
193
194#[derive(Debug)]
195pub enum FrameWindowSplittingConfiguration<ImC> {
196    Quadrupole(QuadWindowExpansionStrategy<ImC>),
197    Window(QuadWindowExpansionStrategy<ImC>),
198}
199
200impl<ImC> Clone for FrameWindowSplittingConfiguration<ImC> {
201    fn clone(&self) -> Self {
202        match self {
203            Self::Quadrupole(s) => Self::Quadrupole(s.clone()),
204            Self::Window(s) => Self::Window(s.clone()),
205        }
206    }
207}
208
209impl<ImC> Default for FrameWindowSplittingConfiguration<ImC> {
210    fn default() -> Self {
211        Self::Quadrupole(QuadWindowExpansionStrategy::Even(1))
212    }
213}
214
215impl<ImC> FrameWindowSplittingConfiguration<ImC> {
216    pub(crate) fn finalize(
217        self,
218        scan_converter: Option<Arc<ImC>>,
219    ) -> FrameWindowSplittingStrategy<ImC> {
220        match self {
221            Self::Quadrupole(x) => FrameWindowSplittingStrategy::Quadrupole(
222                Self::update_im_converter(x, scan_converter),
223            ),
224            Self::Window(x) => FrameWindowSplittingStrategy::Window(
225                Self::update_im_converter(x, scan_converter),
226            ),
227        }
228    }
229
230    fn update_im_converter(
231        quad_strategy: QuadWindowExpansionStrategy<ImC>,
232        scan_converter: Option<Arc<ImC>>,
233    ) -> QuadWindowExpansionStrategy<ImC> {
234        match quad_strategy {
235            QuadWindowExpansionStrategy::UniformMobility((span, step), _) => {
236                QuadWindowExpansionStrategy::UniformMobility(
237                    (span, step),
238                    scan_converter,
239                )
240            },
241            _ => quad_strategy,
242        }
243    }
244}
245
246fn scan_range_subsplit<ImC: InvertibleConverter<ScanIndex, Im>>(
247    start: usize,
248    end: usize,
249    strategy: &QuadWindowExpansionStrategy<ImC>,
250) -> Vec<(usize, usize)> {
251    let out: Vec<(usize, usize)> = match strategy {
252        QuadWindowExpansionStrategy::None => {
253            vec![(start, end)]
254        },
255        QuadWindowExpansionStrategy::Even(num_splits) => {
256            let sub_subwindow_width = (end - start) / (num_splits + 1);
257            let mut out = Vec::new();
258            for sub_subwindow in 0..*num_splits {
259                let sub_subwindow_scan_start =
260                    start + (sub_subwindow_width * sub_subwindow);
261                let sub_subwindow_scan_end =
262                    start + (sub_subwindow_width * (sub_subwindow + 2));
263
264                out.push((sub_subwindow_scan_start, sub_subwindow_scan_end))
265            }
266            out
267        },
268        QuadWindowExpansionStrategy::UniformMobility(
269            (span, step),
270            _converter,
271        ) => {
272            // Since scan start < scan end but low scans are high IMs, we need to
273            // subtract instead of adding.
274            let converter = _converter.clone().unwrap(); // Should always pass if created from FrameWindowConfig
275            let mut curr_start_offset = start;
276            // let mut curr_start_im = converter.convert(curr_start_offset as f64);
277            let mut curr_start_im = f64::from(
278                converter.convert(
279                    ScanIndex::try_from(curr_start_offset)
280                        .expect("ScanIndex conversion out of bounds"),
281                ),
282            );
283
284            let mut curr_end_im = curr_start_im - span;
285            let mut curr_end_offset =
286                usize::from(converter.convert(Im::from(curr_end_im)));
287
288            let mut out = Vec::new();
289            while curr_end_offset < end {
290                out.push((curr_start_offset, curr_end_offset));
291
292                curr_start_im -= step;
293                curr_start_offset =
294                    usize::from(converter.convert(Im::from(curr_start_im)));
295
296                curr_end_im = curr_start_im - span;
297                curr_end_offset =
298                    usize::from(converter.convert(Im::from(curr_end_im)));
299            }
300            if curr_start_offset < end {
301                out.push((curr_start_offset, end));
302            }
303            out
304        },
305        QuadWindowExpansionStrategy::UniformScan((span, step)) => {
306            let mut curr_start_offset = start;
307            let mut curr_end_offset = start + span;
308            let mut out = Vec::new();
309
310            while curr_end_offset < end {
311                out.push((curr_start_offset, curr_end_offset));
312                curr_start_offset += step;
313                curr_end_offset += step;
314            }
315            if curr_start_offset < end {
316                out.push((curr_start_offset, end));
317            }
318            out
319        },
320    };
321
322    debug_assert!(
323        out.iter().all(|(s, e)| s < e),
324        "Invalid scan range: {:?}",
325        out
326    );
327    debug_assert!(
328        out.iter().all(|(s, e)| *s >= start && *e <= end),
329        "Invalid scan range: {:?}",
330        out
331    );
332    out
333}
334
335fn expand_window_settings<ImC: InvertibleConverter<ScanIndex, Im>>(
336    window_groups: &[SqlWindowGroup],
337    quadrupole_settings: &[QuadrupoleSettings],
338    strategy: &QuadWindowExpansionStrategy<ImC>,
339) -> Vec<QuadrupoleSettings> {
340    let mut expanded_quadrupole_settings: Vec<QuadrupoleSettings> = vec![];
341    for window_group in window_groups {
342        let window = window_group.window_group;
343        let frame = window_group.frame;
344        let group = &quadrupole_settings[window - 1];
345        let window_group_start = *group
346            .scan_starts
347            .iter()
348            .min()
349            .expect("SqlReader cannot return empty vecs, so there is always min window_group index");
350        let window_group_end = *group
351            .scan_ends
352            .iter()
353            .max()
354            .expect("SqlReader cannot return empty vecs, so there is always max window_group index");
355        for (sws, swe) in
356            scan_range_subsplit(window_group_start, window_group_end, strategy)
357        {
358            let mut mz_min = f64::MAX;
359            let mut mz_max = f64::MIN;
360            let mut nce_sum = 0.0;
361            let mut total_scan_width = 0.0;
362            for i in 0..group.len() {
363                let gss = group.scan_starts[i];
364                let gse = group.scan_ends[i];
365                if (swe <= gse) || (gss <= sws) {
366                    continue;
367                }
368                let half_isolation_width =
369                    f64::from(group.isolation_windows[i].width()) / 2.0;
370                let isolation_mz =
371                    f64::from(group.isolation_windows[i].center());
372                mz_min = mz_min.min(isolation_mz - half_isolation_width);
373                mz_max = mz_max.max(isolation_mz + half_isolation_width);
374                let scan_width = (gse.min(swe) - gss.max(sws)) as f64;
375                nce_sum +=
376                    group.isolation_windows[i].collision_energy() * scan_width;
377                total_scan_width += scan_width
378            }
379            let isolation_window = IsolationWindow::new_from_bounds(
380                Mz::from(mz_min),
381                Mz::from(mz_max),
382                nce_sum / total_scan_width,
383            );
384            let sub_quad_settings = QuadrupoleSettings {
385                index: frame,
386                scan_starts: vec![sws],
387                scan_ends: vec![swe],
388                isolation_windows: vec![isolation_window],
389            };
390            expanded_quadrupole_settings.push(sub_quad_settings)
391        }
392    }
393    expanded_quadrupole_settings
394}
395
396fn expand_quadrupole_settings<ImC: InvertibleConverter<ScanIndex, Im>>(
397    window_groups: &[SqlWindowGroup],
398    quadrupole_settings: &[QuadrupoleSettings],
399    strategy: &QuadWindowExpansionStrategy<ImC>,
400) -> Vec<QuadrupoleSettings> {
401    let mut expanded_quadrupole_settings: Vec<QuadrupoleSettings> = vec![];
402    for window_group in window_groups {
403        let window = window_group.window_group;
404        let frame = window_group.frame;
405        let group = &quadrupole_settings[window - 1];
406        for sub_window in 0..group.isolation_windows.len() {
407            let subwindow_scan_start = group.scan_starts[sub_window];
408            let subwindow_scan_end = group.scan_ends[sub_window];
409            for (sws, swe) in scan_range_subsplit(
410                subwindow_scan_start,
411                subwindow_scan_end,
412                strategy,
413            ) {
414                let isolation_window = &group.isolation_windows[sub_window];
415                let sub_quad_settings = QuadrupoleSettings {
416                    index: frame,
417                    scan_starts: vec![sws],
418                    scan_ends: vec![swe],
419                    isolation_windows: vec![isolation_window.clone()],
420                };
421                expanded_quadrupole_settings.push(sub_quad_settings)
422            }
423        }
424    }
425    expanded_quadrupole_settings
426}