Skip to main content

mzdata_spectrum/group/
spectrum.rs

1use std::{marker::PhantomData, mem};
2
3use mzpeaks::{CentroidLike, CentroidPeak, DeconvolutedCentroidLike, DeconvolutedPeak};
4
5use crate::{MultiLayerSpectrum, SpectrumLike};
6
7use super::utils::GroupIterState;
8
9
10/// An abstraction over [`SpectrumGroup`](crate::spectrum::SpectrumGroup)'s interface.
11pub trait SpectrumGrouping<
12    C: CentroidLike = CentroidPeak,
13    D: DeconvolutedCentroidLike = DeconvolutedPeak,
14    S: SpectrumLike<C, D> = MultiLayerSpectrum<C, D>,
15>: Default
16{
17    /// Get the precursor spectrum, which may be absent
18    fn precursor(&self) -> Option<&S>;
19    /// Get a mutable reference to the precursor spectrum, which may be absent
20    fn precursor_mut(&mut self) -> Option<&mut S>;
21    /// Explicitly set the precursor spectrum directly.
22    fn set_precursor(&mut self, prec: S);
23
24    /// Get a reference to the collection of product spectra
25    fn products(&self) -> &[S];
26
27    /// Get a mutable reference to the collection of product spectra
28    fn products_mut(&mut self) -> &mut Vec<S>;
29
30    /// The total number of spectra in the group
31    fn total_spectra(&self) -> usize {
32        self.precursor().is_some() as usize + self.products().len()
33    }
34
35    /// The spectrum that occurred first chronologically
36    fn earliest_spectrum(&self) -> Option<&S> {
37        self.precursor().or_else(|| {
38            self.products().iter().min_by(|a, b| {
39                a.acquisition()
40                    .start_time()
41                    .total_cmp(&b.acquisition().start_time())
42            })
43        })
44    }
45
46    /// The spectrum that occurred last chronologically
47    fn latest_spectrum(&self) -> Option<&S> {
48        let product = self.products().iter().max_by(|a, b| {
49            a.acquisition()
50                .start_time()
51                .total_cmp(&b.acquisition().start_time())
52        });
53        match (self.precursor(), product) {
54            (None, None) => None,
55            (None, Some(c)) => Some(c),
56            (Some(p), None) => Some(p),
57            (Some(p), Some(c)) => {
58                if p.start_time() > c.start_time() {
59                    Some(p)
60                } else {
61                    Some(c)
62                }
63            },
64        }
65    }
66
67    /// The lowest MS level in the group. Returns [`None`] if the group is empty or the MS level is 0.
68    fn lowest_ms_level(&self) -> Option<u8> {
69        let prec_level = self.precursor().map(|p| p.ms_level()).unwrap_or(u8::MAX);
70        let val = self
71            .products()
72            .iter()
73            .fold(prec_level, |state, s| state.min(s.ms_level()));
74        if val > 0 {
75            Some(val)
76        } else {
77            None
78        }
79    }
80
81    /// The highest MS level in the group. Returns [`None`] if the group is empty or the MS level is 0.
82    fn highest_ms_level(&self) -> Option<u8> {
83        let prec_level = self
84            .precursor()
85            .map(|p| p.ms_level())
86            .unwrap_or_else(|| u8::MIN);
87        let val = self
88            .products()
89            .iter()
90            .fold(prec_level, |state, s| state.max(s.ms_level()));
91        if val > 0 {
92            Some(val)
93        } else {
94            None
95        }
96    }
97
98    /// Decompose the group into its components, discarding any additional metrics
99    fn into_parts(self) -> (Option<S>, Vec<S>);
100}
101
102/**
103A pairing of an optional MS1 spectrum with all its associated MSn spectra.
104*/
105#[derive(Debug, Clone, PartialEq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107pub struct SpectrumGroup<C = CentroidPeak, D = DeconvolutedPeak, S = MultiLayerSpectrum<C, D>>
108where
109    C: CentroidLike,
110    D: DeconvolutedCentroidLike,
111    S: SpectrumLike<C, D>,
112{
113    /// The MS1 spectrum of a group. This may be absent when the source does not contain any MS1 spectra
114    pub precursor: Option<S>,
115    /// The collection of related MSn spectra. If MSn for n > 2 is used, all levels are present in this
116    /// collection, though there is no ordering guarantee.
117    pub products: Vec<S>,
118    centroid_type: PhantomData<C>,
119    deconvoluted_type: PhantomData<D>,
120}
121
122impl<C, D, S> IntoIterator for SpectrumGroup<C, D, S>
123where
124    C: CentroidLike,
125    D: DeconvolutedCentroidLike,
126    S: SpectrumLike<C, D> + Default,
127{
128    type Item = S;
129
130    type IntoIter = SpectrumGroupIntoIter<C, D, S, Self>;
131
132    fn into_iter(self) -> Self::IntoIter {
133        SpectrumGroupIntoIter::new(self)
134    }
135}
136
137impl<'a, C, D, S> SpectrumGroup<C, D, S>
138where
139    C: CentroidLike,
140    D: DeconvolutedCentroidLike,
141    S: SpectrumLike<C, D>,
142{
143    pub fn new(precursor: Option<S>, products: Vec<S>) -> Self {
144        Self { precursor, products, centroid_type: PhantomData, deconvoluted_type: PhantomData }
145    }
146
147    pub fn iter(&'a self) -> SpectrumGroupIter<'a, C, D, S> {
148        SpectrumGroupIter::new(self)
149    }
150}
151
152/// [`SpectrumGroup`] are traversed visiting [`SpectrumGroup::precursor`]
153/// if it exists and then every entry in [`SpectrumGroup::products`]
154pub struct SpectrumGroupIntoIter<
155    C: CentroidLike = CentroidPeak,
156    D: DeconvolutedCentroidLike = DeconvolutedPeak,
157    S: SpectrumLike<C, D> + Default = MultiLayerSpectrum<C, D>,
158    G: SpectrumGrouping<C, D, S> = SpectrumGroup<C, D, S>,
159> {
160    group: G,
161    state: GroupIterState,
162    _c: PhantomData<C>,
163    _d: PhantomData<D>,
164    _s: PhantomData<S>,
165}
166
167impl<
168        C: CentroidLike,
169        D: DeconvolutedCentroidLike,
170        S: SpectrumLike<C, D> + Default,
171        G: SpectrumGrouping<C, D, S>,
172    > Iterator for SpectrumGroupIntoIter<C, D, S, G>
173{
174    type Item = S;
175
176    fn next(&mut self) -> Option<Self::Item> {
177        {
178            let n = self.n_products();
179            let emission = match self.state {
180                GroupIterState::Precursor => match self.group.precursor_mut() {
181                    Some(prec) => {
182                        if n > 0 {
183                            self.state = GroupIterState::Product(0);
184                        } else {
185                            self.state = GroupIterState::Done;
186                        }
187                        Some(mem::take(prec))
188                    }
189                    None => {
190                        if n > 0 {
191                            self.state = if n > 1 {
192                                GroupIterState::Product(1)
193                            } else {
194                                GroupIterState::Done
195                            };
196                            Some(mem::take(&mut self.group.products_mut()[0]))
197                        } else {
198                            self.state = GroupIterState::Done;
199                            None
200                        }
201                    }
202                },
203                GroupIterState::Product(i) => {
204                    if i < n.saturating_sub(1) {
205                        self.state = GroupIterState::Product(i + 1);
206                        Some(mem::take(&mut self.group.products_mut()[i]))
207                    } else {
208                        self.state = GroupIterState::Done;
209                        Some(mem::take(&mut self.group.products_mut()[i]))
210                    }
211                }
212                GroupIterState::Done => None,
213            };
214            emission
215        }
216    }
217}
218
219impl<
220        C: CentroidLike,
221        D: DeconvolutedCentroidLike,
222        S: SpectrumLike<C, D> + Default,
223        G: SpectrumGrouping<C, D, S>,
224    > SpectrumGroupIntoIter<C, D, S, G>
225{
226    pub fn new(group: G) -> Self {
227        Self {
228            group,
229            state: GroupIterState::Precursor,
230            _c: PhantomData,
231            _d: PhantomData,
232            _s: PhantomData,
233        }
234    }
235
236    fn n_products(&self) -> usize {
237        self.group.products().len()
238    }
239}
240
241/// Iterate over the spectra in [`SpectrumGroup`]. [`SpectrumGroup`] are traversed
242/// visiting [`SpectrumGroup::precursor`] if it exists and then every entry
243/// in [`SpectrumGroup::products`]
244pub struct SpectrumGroupIter<
245    'a,
246    C: CentroidLike = CentroidPeak,
247    D: DeconvolutedCentroidLike = DeconvolutedPeak,
248    S: SpectrumLike<C, D> = MultiLayerSpectrum<C, D>,
249    G: SpectrumGrouping<C, D, S> = SpectrumGroup<C, D, S>,
250> {
251    group: &'a G,
252    state: GroupIterState,
253    _c: PhantomData<C>,
254    _d: PhantomData<D>,
255    _s: PhantomData<S>,
256}
257
258impl<
259        'a,
260        C: CentroidLike,
261        D: DeconvolutedCentroidLike,
262        S: SpectrumLike<C, D> + 'a,
263        G: SpectrumGrouping<C, D, S>,
264    > Iterator for SpectrumGroupIter<'a, C, D, S, G>
265{
266    type Item = &'a S;
267
268    fn next(&mut self) -> Option<Self::Item> {
269        {
270            let n = self.n_products();
271            let emission = match self.state {
272                GroupIterState::Precursor => match self.group.precursor() {
273                    Some(prec) => {
274                        if n > 0 {
275                            self.state = GroupIterState::Product(0);
276                        } else {
277                            self.state = GroupIterState::Done;
278                        }
279                        Some(prec)
280                    }
281                    None => {
282                        if n > 0 {
283                            self.state = if n > 1 {
284                                GroupIterState::Product(1)
285                            } else {
286                                GroupIterState::Done
287                            };
288                            Some(&self.group.products()[0])
289                        } else {
290                            self.state = GroupIterState::Done;
291                            None
292                        }
293                    }
294                },
295                GroupIterState::Product(i) => {
296                    if i < n.saturating_sub(1) {
297                        self.state = GroupIterState::Product(i + 1);
298                        Some(&self.group.products()[i])
299                    } else {
300                        self.state = GroupIterState::Done;
301                        Some(&self.group.products()[i])
302                    }
303                }
304                GroupIterState::Done => None,
305            };
306            emission
307        }
308    }
309}
310
311impl<
312        'a,
313        C: CentroidLike,
314        D: DeconvolutedCentroidLike,
315        S: SpectrumLike<C, D>,
316        G: SpectrumGrouping<C, D, S>,
317    > SpectrumGroupIter<'a, C, D, S, G>
318{
319    pub fn new(group: &'a G) -> Self {
320        Self {
321            group,
322            state: GroupIterState::Precursor,
323            _c: PhantomData,
324            _d: PhantomData,
325            _s: PhantomData,
326        }
327    }
328
329    fn n_products(&self) -> usize {
330        self.group.products().len()
331    }
332}
333
334impl<C: CentroidLike, D: DeconvolutedCentroidLike, S: SpectrumLike<C, D>>
335    Default for SpectrumGroup<C, D, S>
336{
337    fn default() -> Self {
338        Self {
339            precursor: None,
340            products: Vec::new(),
341            centroid_type: PhantomData,
342            deconvoluted_type: PhantomData,
343        }
344    }
345}
346
347impl<C, D, S> SpectrumGrouping<C, D, S> for SpectrumGroup<C, D, S>
348where
349    C: CentroidLike,
350    D: DeconvolutedCentroidLike,
351    S: SpectrumLike<C, D>,
352{
353    fn precursor(&self) -> Option<&S> {
354        match &self.precursor {
355            Some(prec) => Some(prec),
356            None => None,
357        }
358    }
359
360    fn precursor_mut(&mut self) -> Option<&mut S> {
361        match &mut self.precursor {
362            Some(prec) => Some(prec),
363            None => None,
364        }
365    }
366
367    fn set_precursor(&mut self, prec: S) {
368        self.precursor = Some(prec)
369    }
370
371    fn products(&self) -> &[S] {
372        &self.products
373    }
374
375    fn products_mut(&mut self) -> &mut Vec<S> {
376        &mut self.products
377    }
378
379    fn into_parts(self) -> (Option<S>, Vec<S>) {
380        (self.precursor, self.products)
381    }
382}
383
384
385#[cfg(test)]
386mod test {
387    use crate::Spectrum;
388
389    use super::*;
390
391    fn make_group() -> SpectrumGroup {
392        let mut spec1 = Spectrum::default();
393        {
394            let desc1 = spec1.description_mut();
395            desc1.id = "index=0".into();
396            desc1.ms_level = 1;
397            desc1.index = 0;
398            desc1.acquisition.first_scan_mut().unwrap().start_time = 100.0;
399        }
400
401        let mut spec2 = Spectrum::default();
402        {
403            let desc2 = spec2.description_mut();
404            desc2.id = "index=1".into();
405            desc2.ms_level = 2;
406            desc2.index = 1;
407            desc2.acquisition.first_scan_mut().unwrap().start_time = 101.0;
408        }
409
410        let mut spec3 = Spectrum::default();
411        {
412            let desc3 = spec3.description_mut();
413            desc3.id = "index=2".into();
414            desc3.ms_level = 2;
415            desc3.index = 2;
416            desc3.acquisition.first_scan_mut().unwrap().start_time = 101.5;
417        }
418
419        SpectrumGroup::new(Some(spec1), vec![
420            spec2,
421            spec3
422        ])
423    }
424
425    #[test]
426    fn test_construct() {
427        let group = make_group();
428        assert_eq!(group.lowest_ms_level().unwrap(), 1);
429        assert_eq!(group.highest_ms_level().unwrap(), 2);
430
431        assert_eq!(group.earliest_spectrum().unwrap().ms_level(), 1);
432        assert_eq!(group.latest_spectrum().unwrap().ms_level(), 2);
433
434        assert_eq!(group.iter().count(), 3);
435        assert_eq!(group.into_iter().count(), 3);
436    }
437}