Skip to main content

mzdata_spectrum/group/
frame.rs

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