Skip to main content

mzdata_spectrum/
peaks.rs

1use std::borrow::Cow;
2use std::iter::FusedIterator;
3
4use mzpeaks::prelude::*;
5use mzpeaks::{
6    peak::{CentroidPeak, DeconvolutedPeak, MZPoint},
7    peak_set::PeakSetVec,
8    IndexType, MZPeakSetType, MassPeakSetType,
9};
10
11use mzdata_bindata::{ArrayRetrievalError, BinaryArrayMap, BuildFromArrayMap, ArraysAvailable, utils::mass_charge_ratio};
12
13trait SummaryOps {
14    /// Compute the base peak of a spectrum
15    fn base_peak(&self) -> CentroidPeak;
16
17    /// Find the minimum and maximum m/z values of a spectrum
18    fn mz_range(&self) -> (f64, f64);
19
20    /// Compute the total ion current for a spectrum
21    fn tic(&self) -> f32;
22
23    /// Find the point coordinate within the peak layer's coordinate system.
24    ///
25    /// **NOTE**: There is no guarantee that values are stored in m/z order.
26    fn search(&self, query: f64, error_tolerance: Tolerance) -> Option<usize>;
27
28    /// Find the number of points in a profile spectrum, or the number of peaks
29    /// for a centroid spectrum
30    fn len(&self) -> usize;
31
32    /// Get the `i`th point in the peak data, ordered by the peak layer's coordinate system.
33    fn get(&self, i: usize) -> Option<MZPoint>;
34
35    /// Compute summary information about the peak data's m/z range, base peak, total ion current,
36    /// and number of data points
37    fn fetch_summaries(&self) -> SpectrumSummary;
38
39    #[allow(unused)]
40    /// Check if the collection is empty
41    fn is_empty(&self) -> bool;
42}
43
44impl SummaryOps for BinaryArrayMap {
45    fn base_peak(&self) -> CentroidPeak {
46        let mut peak = CentroidPeak::new(0.0, 0.0, 0);
47        if let Ok(intensities) = self.intensities() {
48            let result = intensities
49                .iter()
50                .enumerate()
51                .max_by(|ia, ib| ia.1.total_cmp(ib.1));
52            if let Ok(mzs) = self.mzs() {
53                if let Some((i, inten)) = result {
54                    peak = CentroidPeak::new(mzs[i], *inten, i as IndexType)
55                }
56            }
57        }
58        peak
59    }
60
61    fn mz_range(&self) -> (f64, f64) {
62        if let Ok(mzs) = self.mzs() {
63            if mzs.is_empty() {
64                (0.0, 0.0)
65            } else {
66                (*mzs.first().unwrap(), *mzs.last().unwrap())
67            }
68        } else {
69            (0.0, 0.0)
70        }
71    }
72
73    fn tic(&self) -> f32 {
74        if let Ok(intensities) = self.intensities() {
75            intensities.iter().sum()
76        } else {
77            0.0
78        }
79    }
80
81    fn search(&self, query: f64, error_tolerance: Tolerance) -> Option<usize> {
82        self.search(query, error_tolerance)
83    }
84
85    fn len(&self) -> usize {
86        self.mzs().map(|arr| arr.len()).unwrap_or_default()
87    }
88
89    fn get(&self, i: usize) -> Option<MZPoint> {
90        match (self.mzs(), self.intensities()) {
91            (Ok(mzs), Ok(intensities)) => match (mzs.get(i), intensities.get(i)) {
92                (Some(mz), Some(intensity)) => Some(MZPoint::new(*mz, *intensity)),
93                _ => None,
94            },
95            _ => None,
96        }
97    }
98
99    fn fetch_summaries(&self) -> SpectrumSummary {
100        let (mzs, intensities) = match (self.mzs(), self.intensities()) {
101            (Ok(mzs), Ok(intensities)) => (mzs, intensities),
102            (_, _) => (Cow::Owned(Vec::new()), Cow::Owned(Vec::new())),
103        };
104
105        let (tic, (bpmz, bpint, bpidx)) = mzs.iter().zip(intensities.iter()).enumerate().fold(
106            (0.0, (0.0, 0.0f32, 0)),
107            |(mut tic, (mut bpmz, mut bpint, mut bpidx)), (idx, (mz, int))| {
108                tic += int;
109                if *int > bpint {
110                    bpint = *int;
111                    bpmz = *mz;
112                    bpidx = idx;
113                }
114
115                (tic, (bpmz, bpint, bpidx))
116            },
117        );
118        let mz_range = if mzs.is_empty() {
119            (0.0, 0.0)
120        } else {
121            (mzs.first().copied().unwrap(), mzs.last().copied().unwrap())
122        };
123        SpectrumSummary::new(
124            tic,
125            CentroidPeak::new(bpmz, bpint, bpidx as u32),
126            mz_range,
127            mzs.len(),
128        )
129    }
130
131    fn is_empty(&self) -> bool {
132        self.len() == 0
133    }
134}
135
136impl<C: CentroidLike> SummaryOps for MZPeakSetType<C> {
137    fn base_peak(&self) -> CentroidPeak {
138        let result = self
139            .iter()
140            .enumerate()
141            .max_by(|ia, ib| ia.1.intensity().partial_cmp(&ib.1.intensity()).unwrap());
142        if let Some((i, peak)) = result {
143            CentroidPeak::new(peak.coordinate(), peak.intensity(), i as IndexType)
144        } else {
145            CentroidPeak::new(0.0, 0.0, 0)
146        }
147    }
148
149    fn mz_range(&self) -> (f64, f64) {
150        if PeakCollection::is_empty(self) {
151            (0.0, 0.0)
152        } else {
153            (
154                self.first().as_ref().unwrap().mz(),
155                self.last().as_ref().unwrap().mz(),
156            )
157        }
158    }
159
160    fn tic(&self) -> f32 {
161        self.iter().map(|p| p.intensity()).sum()
162    }
163
164    fn search(&self, query: f64, error_tolerance: Tolerance) -> Option<usize> {
165        PeakCollection::search(self, query, error_tolerance)
166    }
167
168    fn len(&self) -> usize {
169        PeakCollection::len(self)
170    }
171
172    fn get(&self, i: usize) -> Option<MZPoint> {
173        self.peaks
174            .get(i)
175            .map(|p| MZPoint::new(p.mz(), p.intensity()))
176    }
177
178    fn fetch_summaries(&self) -> SpectrumSummary {
179        let state = self.iter().fold(
180            (
181                0.0f32,
182                CentroidPeak::default(),
183                (f64::INFINITY, f64::NEG_INFINITY),
184            ),
185            |(mut tic, mut bp, (mut mz_min, mut mz_max)), p| {
186                tic += p.intensity();
187                if p.intensity() > bp.intensity {
188                    bp = p.as_centroid();
189                }
190                let mz = p.mz();
191                mz_min = mz_min.min(mz);
192                mz_max = mz_max.max(mz);
193                (tic, bp, (mz_min, mz_max))
194            },
195        );
196
197        SpectrumSummary::new(state.0, state.1, state.2, SummaryOps::len(self))
198    }
199
200    fn is_empty(&self) -> bool {
201        PeakCollection::is_empty(self)
202    }
203}
204
205impl<D: DeconvolutedCentroidLike> SummaryOps for MassPeakSetType<D> {
206    fn base_peak(&self) -> CentroidPeak {
207        let result = self
208            .iter()
209            .enumerate()
210            .max_by(|ia, ib| ia.1.intensity().partial_cmp(&ib.1.intensity()).unwrap());
211        if let Some((i, peak)) = result {
212            CentroidPeak::new(
213                mass_charge_ratio(peak.coordinate(), peak.charge()),
214                peak.intensity(),
215                i as IndexType,
216            )
217        } else {
218            CentroidPeak::new(0.0, 0.0, 0)
219        }
220    }
221
222    fn mz_range(&self) -> (f64, f64) {
223        if PeakCollection::is_empty(self) {
224            (0.0, 0.0)
225        } else {
226            self.iter()
227                .map(|p| {
228                    let m = p.neutral_mass();
229                    let z = p.charge();
230                    mass_charge_ratio(m, z)
231                })
232                .fold((f64::MAX, f64::MIN), |state, mz| {
233                    (state.0.min(mz), state.1.max(mz))
234                })
235        }
236    }
237
238    fn tic(&self) -> f32 {
239        self.iter().map(|p| p.intensity()).sum()
240    }
241
242    fn search(&self, query: f64, error_tolerance: Tolerance) -> Option<usize> {
243        PeakCollection::search(self, query, error_tolerance)
244    }
245
246    fn len(&self) -> usize {
247        PeakCollection::len(self)
248    }
249
250    fn get(&self, i: usize) -> Option<MZPoint> {
251        self.peaks.get(i).map(|p| {
252            MZPoint::new(
253                mass_charge_ratio(p.neutral_mass(), p.charge()),
254                p.intensity(),
255            )
256        })
257    }
258
259    fn fetch_summaries(&self) -> SpectrumSummary {
260        let state = self.iter().fold(
261            (
262                0.0f32,
263                CentroidPeak::default(),
264                (f64::INFINITY, f64::NEG_INFINITY),
265            ),
266            |(mut tic, mut bp, (mut mz_min, mut mz_max)), p| {
267                tic += p.intensity();
268                let mz = mass_charge_ratio(p.neutral_mass(), p.charge());
269                if p.intensity() > bp.intensity {
270                    bp.intensity = p.intensity();
271                    bp.index = p.get_index();
272                    bp.mz = mz;
273                }
274                mz_min = mz_min.min(mz);
275                mz_max = mz_max.max(mz);
276                (tic, bp, (mz_min, mz_max))
277            },
278        );
279
280        SpectrumSummary::new(state.0, state.1, state.2, PeakCollection::len(self))
281    }
282
283    fn is_empty(&self) -> bool {
284        PeakCollection::is_empty(self)
285    }
286}
287
288/// Represent an owned representation of one the kinds of peak data that a [`SpectrumLike`](crate::spectrum::SpectrumLike) instance
289/// might otherwise carry.
290///
291/// # See also
292/// [`RefPeakDataLevel`] for borrowed version.
293#[derive(Debug)]
294#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
295pub enum PeakDataLevel<
296    C: CentroidLike = CentroidPeak,
297    D: DeconvolutedCentroidLike = DeconvolutedPeak,
298> {
299    /// No signal data is present
300    Missing,
301    /// Raw [`BinaryArrayMap`] exists
302    RawData(BinaryArrayMap),
303    /// A m/z centroid peak list exists
304    Centroid(MZPeakSetType<C>),
305    /// A neutral mass peak list exists
306    Deconvoluted(MassPeakSetType<D>),
307}
308
309impl<C: CentroidLike, D: DeconvolutedCentroidLike> PeakDataLevel<C, D> {
310    /// Iterate over [`MZPoint`](mzpeaks::peak::MZPoint) data encoded in the peak data.
311    ///
312    /// **NOTE**: Values are produced in the order they are stored, so the data are not guaranteed
313    /// to be ordered by m/z.
314    pub fn iter(&self) -> PeakDataIterDispatch<'_, C, D> {
315        match self {
316            PeakDataLevel::Missing => PeakDataIterDispatch::PeakData(PeakDataIter::new(self)),
317            PeakDataLevel::RawData(a) => {
318                PeakDataIterDispatch::RawData(RawIter::from_binary_array_map(a).unwrap())
319            }
320            PeakDataLevel::Centroid(_) => PeakDataIterDispatch::PeakData(PeakDataIter::new(self)),
321            PeakDataLevel::Deconvoluted(_) => {
322                PeakDataIterDispatch::PeakData(PeakDataIter::new(self))
323            }
324        }
325    }
326
327    /// Attempt to reconstruct one of the peak levels based upon the available data arrays
328    pub(crate) fn try_build_peaks(arrays: &BinaryArrayMap) -> Result<Self, ArrayRetrievalError>
329    where
330        C: BuildFromArrayMap,
331        D: BuildFromArrayMap,
332    {
333        {
334            if let ArraysAvailable::Ok = D::has_arrays_for(arrays) {
335                let peaks = D::try_from_arrays(arrays)?.into();
336                Ok(Self::Deconvoluted(peaks))
337            } else if let ArraysAvailable::Ok = C::has_arrays_for(arrays) {
338                let peaks = C::try_from_arrays(arrays)?.into();
339                Ok(Self::Centroid(peaks))
340            } else {
341                Ok(Self::Missing)
342            }
343        }
344    }
345}
346
347impl<C: CentroidLike + BuildFromArrayMap, D: DeconvolutedCentroidLike + BuildFromArrayMap>
348    TryFrom<&BinaryArrayMap> for PeakDataLevel<C, D>
349{
350    type Error = ArrayRetrievalError;
351
352    fn try_from(value: &BinaryArrayMap) -> Result<Self, Self::Error> {
353        Self::try_build_peaks(value)
354    }
355}
356
357impl<C: CentroidLike, D: DeconvolutedCentroidLike> PeakDataLevel<C, D> {
358    /// Compute the base peak of a spectrum
359    pub fn base_peak(&self) -> CentroidPeak {
360        match self {
361            Self::Missing => CentroidPeak::new(0.0, 0.0, 0),
362            Self::RawData(arrays) => arrays.base_peak(),
363            Self::Centroid(peaks) => SummaryOps::base_peak(peaks),
364            Self::Deconvoluted(peaks) => SummaryOps::base_peak(peaks),
365        }
366    }
367
368    /// Find the minimum and maximum m/z values of a spectrum
369    pub fn mz_range(&self) -> (f64, f64) {
370        match self {
371            Self::Missing => (0.0, 0.0),
372            Self::RawData(arrays) => arrays.mz_range(),
373            Self::Centroid(peaks) => peaks.mz_range(),
374            Self::Deconvoluted(peaks) => peaks.mz_range(),
375        }
376    }
377
378    /// Compute the total ion current for a spectrum
379    pub fn tic(&self) -> f32 {
380        match self {
381            Self::Missing => 0.0,
382            Self::RawData(arrays) => arrays.tic(),
383            Self::Centroid(peaks) => peaks.tic(),
384            Self::Deconvoluted(peaks) => peaks.tic(),
385        }
386    }
387
388    /// Iterate over [`MZPoint`](mzpeaks::peak::MZPoint) data encoded in the peak data.
389    ///
390    /// **NOTE**: The [`Self::Deconvoluted`] layer uses the neutral mass coordinate system compared to the others, it should
391    /// be explicitly tested against.
392    pub fn search(&self, query: f64, error_tolerance: Tolerance) -> Option<usize> {
393        match self {
394            Self::Missing => None,
395            Self::RawData(arrays) => arrays.search(query, error_tolerance),
396            Self::Centroid(peaks) => SummaryOps::search(peaks, query, error_tolerance),
397            Self::Deconvoluted(peaks) => SummaryOps::search(peaks, query, error_tolerance),
398        }
399    }
400
401    /// Find the number of points in a profile spectrum, or the number of peaks
402    /// for a centroid spectrum
403    pub fn len(&self) -> usize {
404        match self {
405            Self::Missing => 0,
406            Self::RawData(arrays) => SummaryOps::len(arrays),
407            Self::Centroid(peaks) => SummaryOps::len(peaks),
408            Self::Deconvoluted(peaks) => SummaryOps::len(peaks),
409        }
410    }
411
412    /// Get the `i`th point in the peak data.
413    ///
414    /// **NOTE**: There is no guarantee that values are stored in m/z order.
415    pub fn get(&self, i: usize) -> Option<MZPoint> {
416        match self {
417            PeakDataLevel::Missing => None,
418            PeakDataLevel::RawData(arr) => SummaryOps::get(arr, i),
419            PeakDataLevel::Centroid(peaks) => peaks.get(i),
420            PeakDataLevel::Deconvoluted(peaks) => peaks.get(i),
421        }
422    }
423
424    /// Compute summary information about the peak data's m/z range, base peak, total ion current,
425    /// and number of data points
426    pub fn fetch_summaries(&self) -> SpectrumSummary {
427        match self {
428            Self::Missing => SpectrumSummary::new(0.0, CentroidPeak::default(), (0.0, 0.0), 0),
429            Self::RawData(arrays) => arrays.fetch_summaries(),
430            Self::Centroid(peaks) => peaks.fetch_summaries(),
431            Self::Deconvoluted(peaks) => peaks.fetch_summaries(),
432        }
433    }
434
435    /// Check if the collection is empty
436    pub fn is_empty(&self) -> bool {
437        self.len() == 0
438    }
439}
440
441/// An [`MZPoint`] iterator over paired, potentially borrowed, arrays of m/z and intensity data that may not have been
442/// extracted into discrete peaks.
443///
444/// This type should not need to be used directly in most cases. Instead see [`PeakDataIterDispatch`]
445pub struct RawIter<'a> {
446    mz_array: Cow<'a, [f64]>,
447    intensity_array: Cow<'a, [f32]>,
448    n: usize,
449    i: usize,
450}
451
452impl<'a> RawIter<'a> {
453    fn new(mz_array: Cow<'a, [f64]>, intensity_array: Cow<'a, [f32]>) -> Self {
454        let n = mz_array.len();
455        let i = 0;
456        Self {
457            mz_array,
458            intensity_array,
459            n,
460            i,
461        }
462    }
463
464    fn from_binary_array_map(arrays: &'a BinaryArrayMap) -> Result<Self, ArrayRetrievalError> {
465        let mz_array = arrays.mzs()?;
466        let intensity_array = arrays.intensities()?;
467
468        Ok(Self::new(mz_array, intensity_array))
469    }
470}
471
472impl Iterator for RawIter<'_> {
473    type Item = MZPoint;
474
475    fn next(&mut self) -> Option<Self::Item> {
476        if self.i >= self.n {
477            None
478        } else {
479            let i = self.i;
480            let mz = self.mz_array[i];
481            let intens = self.intensity_array[i];
482            self.i += 1;
483            Some(MZPoint::new(mz, intens))
484        }
485    }
486}
487
488/// An [`MZPoint`] iterator over owned peak collections abstracted over by [`PeakDataLevel`]
489///
490/// This type should not need to be used directly in most cases. Instead see [`PeakDataIterDispatch`]
491pub struct PeakDataIter<'a, C: CentroidLike, D: DeconvolutedCentroidLike> {
492    peaks: &'a PeakDataLevel<C, D>,
493    i: usize,
494    n: usize,
495}
496
497impl<C: CentroidLike, D: DeconvolutedCentroidLike> Iterator for PeakDataIter<'_, C, D> {
498    type Item = MZPoint;
499
500    fn next(&mut self) -> Option<Self::Item> {
501        let i = self.i;
502        if i >= self.n {
503            None
504        } else {
505            self.i += 1;
506            self.get(i)
507        }
508    }
509}
510
511impl<C: CentroidLike, D: DeconvolutedCentroidLike> ExactSizeIterator for PeakDataIter<'_, C, D> {
512    fn len(&self) -> usize {
513        self.n - self.i
514    }
515}
516
517impl<C: CentroidLike, D: DeconvolutedCentroidLike> FusedIterator for PeakDataIter<'_, C, D> {}
518
519impl<C: CentroidLike, D: DeconvolutedCentroidLike> DoubleEndedIterator for PeakDataIter<'_, C, D> {
520    fn next_back(&mut self) -> Option<Self::Item> {
521        let n = self.n;
522        if self.i >= n {
523            None
524        } else {
525            let pt = self.get(n - 1);
526            self.n -= 1;
527            pt
528        }
529    }
530}
531
532impl<'a, C: CentroidLike, D: DeconvolutedCentroidLike> PeakDataIter<'a, C, D> {
533    pub fn new(peaks: &'a PeakDataLevel<C, D>) -> Self {
534        let n = peaks.len();
535        Self { peaks, i: 0, n }
536    }
537
538    pub fn get(&self, i: usize) -> Option<MZPoint> {
539        self.peaks.get(i)
540    }
541}
542
543/// An [`MZPoint`] iterator over peak collections abstracted by source type and ownership status.
544///
545/// This iterator strategy has a slight amount of overhead on each operation as it dispatches to the
546/// appropriate underlying type. It is necessary to allow [`PeakDataLevel`] and [`RefPeakDataLevel`]
547/// to have a single type to return for an iterator.
548pub enum PeakDataIterDispatch<'a, C: CentroidLike, D: DeconvolutedCentroidLike> {
549    PeakData(PeakDataIter<'a, C, D>),
550    RawData(RawIter<'a>),
551    RefPeakData(RefPeakDataIter<'a, C, D>),
552}
553
554impl<C: CentroidLike, D: DeconvolutedCentroidLike> Iterator for PeakDataIterDispatch<'_, C, D> {
555    type Item = MZPoint;
556
557    fn next(&mut self) -> Option<Self::Item> {
558        match self {
559            PeakDataIterDispatch::PeakData(a) => a.next(),
560            PeakDataIterDispatch::RawData(a) => a.next(),
561            PeakDataIterDispatch::RefPeakData(a) => a.next(),
562        }
563    }
564}
565
566#[derive(Debug)]
567/// An variant for dispatching to different strategies of computing
568/// common statistics of different levels of peak data.
569pub enum RefPeakDataLevel<'a, C: CentroidLike, D: DeconvolutedCentroidLike> {
570    /// No signal data is present
571    Missing,
572    /// Raw [`BinaryArrayMap`] exists
573    RawData(&'a BinaryArrayMap),
574    /// A m/z centroid peak list exists
575    Centroid(&'a MZPeakSetType<C>),
576    /// A neutral mass peak list exists
577    Deconvoluted(&'a MassPeakSetType<D>),
578}
579
580/// A set of common summary metrics describing a mass spectrum
581#[derive(Debug, Default, Clone)]
582pub struct SpectrumSummary {
583    /// The total ion current for a spectrum
584    pub tic: f32,
585    /// The base peak, the most intense peak in the spectrum
586    pub base_peak: CentroidPeak,
587    /// The minimum and maximum m/z observed
588    pub mz_range: (f64, f64),
589    /// The number of peaks or data points in the spectrum
590    pub count: usize,
591}
592
593impl SpectrumSummary {
594    pub fn new(tic: f32, base_peak: CentroidPeak, mz_range: (f64, f64), count: usize) -> Self {
595        Self {
596            tic,
597            base_peak,
598            mz_range,
599            count,
600        }
601    }
602
603    pub fn len(&self) -> usize {
604        self.count
605    }
606
607    pub fn is_empty(&self) -> bool {
608        self.count == 0
609    }
610}
611
612impl<C: CentroidLike, D: DeconvolutedCentroidLike> RefPeakDataLevel<'_, C, D> {
613    /// Compute the base peak of a spectrum
614    pub fn base_peak(&self) -> CentroidPeak {
615        match self {
616            RefPeakDataLevel::Missing => CentroidPeak::new(0.0, 0.0, 0),
617            RefPeakDataLevel::RawData(arrays) => arrays.base_peak(),
618            RefPeakDataLevel::Centroid(peaks) => SummaryOps::base_peak(*peaks),
619            RefPeakDataLevel::Deconvoluted(peaks) => SummaryOps::base_peak(*peaks),
620        }
621    }
622
623    /// Find the minimum and maximum m/z values of a spectrum
624    pub fn mz_range(&self) -> (f64, f64) {
625        match self {
626            Self::Missing => (0.0, 0.0),
627            Self::RawData(arrays) => arrays.mz_range(),
628            Self::Centroid(peaks) => peaks.mz_range(),
629            Self::Deconvoluted(peaks) => peaks.mz_range(),
630        }
631    }
632
633    /// Compute the total ion current for a spectrum
634    pub fn tic(&self) -> f32 {
635        match self {
636            Self::Missing => 0.0,
637            Self::RawData(arrays) => arrays.tic(),
638            Self::Centroid(peaks) => peaks.tic(),
639            Self::Deconvoluted(peaks) => peaks.tic(),
640        }
641    }
642
643    /// Find the point coordinate within the peak layer's coordinate system.
644    ///
645    /// **NOTE**: The [`Self::Deconvoluted`] layer uses the neutral mass coordinate system compared to the others, it should
646    /// be explicitly tested against.
647    pub fn search(&self, query: f64, error_tolerance: Tolerance) -> Option<usize> {
648        match self {
649            Self::Missing => None,
650            Self::RawData(arrays) => arrays.search(query, error_tolerance),
651            Self::Centroid(peaks) => SummaryOps::search(*peaks, query, error_tolerance),
652            Self::Deconvoluted(peaks) => SummaryOps::search(*peaks, query, error_tolerance),
653        }
654    }
655
656    /// Find the number of points in a profile spectrum, or the number of peaks
657    /// for a centroid spectrum
658    pub fn len(&self) -> usize {
659        match self {
660            RefPeakDataLevel::Missing => 0,
661            RefPeakDataLevel::RawData(arrays) => SummaryOps::len(*arrays),
662            RefPeakDataLevel::Centroid(peaks) => SummaryOps::len(*peaks),
663            RefPeakDataLevel::Deconvoluted(peaks) => SummaryOps::len(*peaks),
664        }
665    }
666
667    /// Check if the collection is empty
668    pub fn is_empty(&self) -> bool {
669        self.len() == 0
670    }
671
672    /// Iterate over [`MZPoint`](mzpeaks::peak::MZPoint) data encoded in the peak data.
673    ///
674    /// **NOTE**: Values are produced in the order they are stored, so the data are not guaranteed
675    /// to be ordered by m/z.
676    pub fn iter(&self) -> PeakDataIterDispatch<'_, C, D> {
677        match self {
678            RefPeakDataLevel::Missing => {
679                PeakDataIterDispatch::RefPeakData(RefPeakDataIter::new(self))
680            }
681            RefPeakDataLevel::RawData(a) => {
682                PeakDataIterDispatch::RawData(RawIter::from_binary_array_map(a).unwrap())
683            }
684            RefPeakDataLevel::Centroid(_) => {
685                PeakDataIterDispatch::RefPeakData(RefPeakDataIter::new(self))
686            }
687            RefPeakDataLevel::Deconvoluted(_) => {
688                PeakDataIterDispatch::RefPeakData(RefPeakDataIter::new(self))
689            }
690        }
691    }
692
693    /// Get the `i`th point in the peak data.
694    ///
695    /// **NOTE**: There is no guarantee that values are stored in m/z order.
696    pub fn get(&self, i: usize) -> Option<MZPoint> {
697        match self {
698            RefPeakDataLevel::Missing => None,
699            RefPeakDataLevel::RawData(arr) => SummaryOps::get(*arr, i),
700            RefPeakDataLevel::Centroid(peaks) => peaks.get(i),
701            RefPeakDataLevel::Deconvoluted(peaks) => peaks.get(i),
702        }
703    }
704
705    /// Compute summary information about the peak data's m/z range, base peak, total ion current,
706    /// and number of data points
707    pub fn fetch_summaries(&self) -> SpectrumSummary {
708        match self {
709            RefPeakDataLevel::Missing => {
710                SpectrumSummary::new(0.0, CentroidPeak::default(), (0.0, 0.0), 0)
711            }
712            RefPeakDataLevel::RawData(arrays) => arrays.fetch_summaries(),
713            RefPeakDataLevel::Centroid(peaks) => peaks.fetch_summaries(),
714            RefPeakDataLevel::Deconvoluted(peaks) => peaks.fetch_summaries(),
715        }
716    }
717}
718
719impl<C: CentroidLike + Clone, D: DeconvolutedCentroidLike + Clone> RefPeakDataLevel<'_, C, D> {
720    pub fn cloned(&self) -> PeakDataLevel<C, D> {
721        match self {
722            RefPeakDataLevel::Missing => PeakDataLevel::Missing,
723            RefPeakDataLevel::RawData(a) => PeakDataLevel::RawData(BinaryArrayMap::clone(a)),
724            RefPeakDataLevel::Centroid(a) => PeakDataLevel::Centroid(PeakSetVec::clone(a)),
725            RefPeakDataLevel::Deconvoluted(a) => PeakDataLevel::Deconvoluted(PeakSetVec::clone(a)),
726        }
727    }
728}
729
730/// An [`MZPoint`] iterator over borrowed peak collections abstracted over by [`RefPeakDataLevel`]
731///
732/// This type should not need to be used directly in most cases. Instead see [`PeakDataIterDispatch`]
733pub struct RefPeakDataIter<'a, C: CentroidLike, D: DeconvolutedCentroidLike> {
734    peaks: &'a RefPeakDataLevel<'a, C, D>,
735    i: usize,
736    n: usize,
737}
738
739impl<C: CentroidLike, D: DeconvolutedCentroidLike> Iterator for RefPeakDataIter<'_, C, D> {
740    type Item = MZPoint;
741
742    fn next(&mut self) -> Option<Self::Item> {
743        let i = self.i;
744        if i >= self.n {
745            None
746        } else {
747            self.i += 1;
748            self.get(i)
749        }
750    }
751}
752
753impl<C: CentroidLike, D: DeconvolutedCentroidLike> ExactSizeIterator for RefPeakDataIter<'_, C, D> {
754    fn len(&self) -> usize {
755        self.n - self.i
756    }
757}
758
759impl<C: CentroidLike, D: DeconvolutedCentroidLike> FusedIterator for RefPeakDataIter<'_, C, D> {}
760
761impl<C: CentroidLike, D: DeconvolutedCentroidLike> DoubleEndedIterator
762    for RefPeakDataIter<'_, C, D>
763{
764    fn next_back(&mut self) -> Option<Self::Item> {
765        let n = self.n;
766        if self.i >= n {
767            None
768        } else {
769            let pt = self.get(n - 1);
770            self.n -= 1;
771            pt
772        }
773    }
774}
775
776impl<'a, C: CentroidLike, D: DeconvolutedCentroidLike> RefPeakDataIter<'a, C, D> {
777    pub fn new(peaks: &'a RefPeakDataLevel<'a, C, D>) -> Self {
778        let n = peaks.len();
779        Self { peaks, i: 0, n }
780    }
781
782    pub fn get(&self, i: usize) -> Option<MZPoint> {
783        self.peaks.get(i)
784    }
785}
786
787#[cfg(test)]
788mod test {
789    use super::*;
790    use mzdata_bindata::BuildArrayMapFrom;
791
792    fn check_remaining(
793        mut it: impl ExactSizeIterator<Item = MZPoint> + DoubleEndedIterator,
794        expected: &[MZPoint],
795    ) {
796        let mut expected = expected.iter().cloned();
797        loop {
798            let n = expected.len();
799            assert_eq!(it.len(), n);
800            assert_eq!(it.next(), expected.next());
801            assert_eq!(it.len(), expected.len());
802            assert_eq!(it.next_back(), expected.next_back());
803            if n == 0 {
804                break;
805            }
806        }
807        assert_eq!(it.next_back(), None);
808        assert_eq!(it.next(), None);
809    }
810
811    #[test]
812    fn test_peak_iterator_remaining() {
813        for n in 0..5 {
814            let peaks: Vec<_> = (0..n)
815                .map(|i| CentroidPeak::new(100.0 + i as f64, i as f32, i))
816                .collect();
817            let expected: Vec<_> = peaks
818                .iter()
819                .map(|p| MZPoint::new(p.mz, p.intensity))
820                .collect();
821            let raw: PeakDataLevel = PeakDataLevel::RawData(CentroidPeak::as_arrays(&peaks));
822            let centroid: PeakDataLevel = PeakDataLevel::Centroid(PeakSetVec::new(peaks));
823            for data in [&raw, &centroid] {
824                check_remaining(PeakDataIter::new(data), &expected);
825                let borrowed: RefPeakDataLevel<CentroidPeak, DeconvolutedPeak> = match data {
826                    PeakDataLevel::RawData(a) => RefPeakDataLevel::RawData(a),
827                    PeakDataLevel::Centroid(a) => RefPeakDataLevel::Centroid(a),
828                    _ => unreachable!(),
829                };
830                check_remaining(RefPeakDataIter::new(&borrowed), &expected);
831            }
832        }
833        let missing: PeakDataLevel = PeakDataLevel::Missing;
834        check_remaining(PeakDataIter::new(&missing), &[]);
835        let missing: RefPeakDataLevel<CentroidPeak, DeconvolutedPeak> = RefPeakDataLevel::Missing;
836        check_remaining(RefPeakDataIter::new(&missing), &[]);
837    }
838}