Skip to main content

mzdata_spectrum/
chromatogram.rs

1use std::borrow::{Borrow, Cow};
2
3use mzdata_bindata::{ArrayRetrievalError, ArrayType, BinaryArrayMap, ByteArrayView};
4use mzdata_param::{Param, ParamDescribed};
5use crate::scan_properties::{
6    ChromatogramDescription, ChromatogramType, Precursor, ScanPolarity, Product
7};
8use mzpeaks::coordinate::{Time, MZ};
9use mzpeaks::feature::{FeatureView, SimpleFeature, TimeInterval};
10
11
12/// Chromatograms record measures over time, and may be derived from mass spectra or
13/// other forms of detectors.
14///
15/// Chromatograms might be better called "traces".
16#[derive(Debug, Default, Clone)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Chromatogram {
19    description: ChromatogramDescription,
20    pub arrays: BinaryArrayMap,
21}
22
23macro_rules! as_feature_view {
24    ($chromatogram:ident, $view:ident => $then:tt) => {
25        if let Ok(t) = $chromatogram.time() {
26            if let Ok(i) = $chromatogram.intensity() {
27                let $view = FeatureView::<Time, Time>::new(t.borrow(), t.borrow(), i.borrow());
28                Some($then)
29            } else {
30                None
31            }
32        } else {
33            None
34        }
35    };
36}
37
38#[allow(unused)]
39pub(crate) fn as_simple_feature(chromatogram: &Chromatogram) -> Option<SimpleFeature<MZ, Time>> {
40    if let Ok(t) = chromatogram.time() {
41        if let Ok(i) = chromatogram.intensity() {
42            let mut f = SimpleFeature::<MZ, Time>::empty(0.0);
43            f.extend(t.iter().zip(i.iter()).map(|(y, z)| (0.0f64, *y, *z)));
44            return Some(f);
45        }
46    }
47    None
48}
49
50impl TimeInterval<Time> for Chromatogram {
51    fn start_time(&self) -> Option<f64> {
52        if let Ok(t) = self.time() {
53            t.first().copied()
54        } else {
55            None
56        }
57    }
58
59    fn end_time(&self) -> Option<f64> {
60        if let Ok(t) = self.time() {
61            t.last().copied()
62        } else {
63            None
64        }
65    }
66
67    fn apex_time(&self) -> Option<f64> {
68        as_feature_view!(self, view => {
69            view.apex_time()
70        })?
71    }
72
73    fn area(&self) -> f32 {
74        as_feature_view!(self, view => {
75            view.area()
76        })
77        .unwrap_or_default()
78    }
79
80    fn iter_time(&self) -> impl Iterator<Item = f64> {
81        self.arrays
82            .get(&ArrayType::TimeArray)
83            .map(|a| a.iter_f64().unwrap())
84            .into_iter()
85            .flatten()
86    }
87}
88
89
90/// Analog of [`SpectrumLike`](crate::spectrum::SpectrumLike) for chromatograms or
91/// other measures over time.
92pub trait ChromatogramLike: ParamDescribed {
93    /// The method to access the chromatogram description itself, which supplies
94    /// the data for most other methods on this trait.
95    fn description(&self) -> &ChromatogramDescription;
96
97    /// The method to mutably access the chromatogram description itself, which supplies
98    /// the data for most other methods on this trait.
99    fn description_mut(&mut self) -> &mut ChromatogramDescription;
100
101    /// Access the (first) precursor information, if it exists.
102    #[inline]
103    fn precursor(&self) -> Option<&Precursor> {
104        let desc = self.description();
105        desc.precursor.first()
106    }
107
108    /// Access the (first) product, if it exists
109    fn product(&self) -> Option<&Product> {
110        let desc = self.description();
111        desc.products.first()
112    }
113
114    /// Iterate over all precursors of the chromatogram
115    fn precursor_iter(&self) -> impl Iterator<Item = &Precursor> {
116        let desc = self.description();
117        desc.precursor.iter()
118    }
119
120    /// Mutably access the (first) precursor information, if it exists
121    fn precursor_mut(&mut self) -> Option<&mut Precursor> {
122        let desc = self.description_mut();
123        desc.precursor.first_mut()
124    }
125
126    /// Mutably access the (first) product, if it exists
127    fn product_mut(&mut self) -> Option<&mut Product> {
128        let desc = self.description_mut();
129        desc.products.first_mut()
130    }
131
132    /// Iterate over all precursors of the chromatogram mutably
133    fn precursor_iter_mut(&mut self) -> impl Iterator<Item = &mut Precursor> {
134        let desc = self.description_mut();
135        desc.precursor.iter_mut()
136    }
137
138    /// Add a precursor to the list of precursors for this chromatogram.
139    ///
140    /// Precursors beyond the first one correspond to lower exponentiated spectra, e.g. for an MS3 chromatogram
141    /// the first precursor is the MS2 product ion that was selected, and the second precursor corresponds
142    /// to the original MS1 ion that was chosen for MS2.
143    ///
144    /// Higher order precursors may be accessed with [`ChromatogramLike::precursor_iter`].
145    fn add_precursor(&mut self, precursor: Precursor) {
146        self.description_mut().precursor.push(precursor);
147    }
148
149    /// Remove the precursor entry at `index` in the precursor list.
150    ///
151    /// Care should be taken if you are attempting to re-order precursors.
152    /// It may be simpler to use [`ChromatogramLike::precursor_iter_mut`] to
153    /// re-arrange entries.
154    fn remove_precursor(&mut self, index: usize) -> Precursor {
155        self.description_mut().precursor.remove(index)
156    }
157
158    #[inline]
159    fn start_time(&self) -> Option<f64> {
160        if let Ok(t) = self.time() {
161            t.first().copied()
162        } else {
163            None
164        }
165    }
166
167    #[inline]
168    fn end_time(&self) -> Option<f64> {
169        if let Ok(t) = self.time() {
170            t.last().copied()
171        } else {
172            None
173        }
174    }
175
176    /// Access the MS exponentiation level
177    #[inline]
178    fn ms_level(&self) -> Option<u8> {
179        self.description().ms_level
180    }
181
182    /// Access the native ID string for the chromatogram
183    #[inline]
184    fn id(&self) -> &str {
185        &self.description().id
186    }
187
188    #[inline]
189    fn chromatogram_type(&self) -> ChromatogramType {
190        self.description().chromatogram_type
191    }
192
193    /// Access the index of the chromatogram in the source file
194    #[inline]
195    fn index(&self) -> usize {
196        self.description().index
197    }
198
199    #[inline]
200    fn polarity(&self) -> ScanPolarity {
201        self.description().polarity
202    }
203
204    fn is_aggregate(&self) -> bool {
205        self.description().is_aggregate()
206    }
207
208    fn is_electromagnetic_radiation(&self) -> bool {
209        self.description().is_electromagnetic_radiation()
210    }
211
212    fn is_ion_current(&self) -> bool {
213        self.description().is_ion_current()
214    }
215
216    fn time(&self) -> Result<Cow<'_, [f64]>, ArrayRetrievalError>;
217    fn intensity(&self) -> Result<Cow<'_, [f32]>, ArrayRetrievalError>;
218}
219
220impl Chromatogram {
221    pub fn new(description: ChromatogramDescription, arrays: BinaryArrayMap) -> Self {
222        Self {
223            description,
224            arrays,
225        }
226    }
227
228    pub fn time(&self) -> Result<Cow<'_, [f64]>, ArrayRetrievalError> {
229        if let Some(a) = self.arrays.get(&ArrayType::TimeArray) {
230            a.to_f64()
231        } else {
232            Err(ArrayRetrievalError::NotFound(ArrayType::TimeArray))
233        }
234    }
235
236    pub fn intensity(&self) -> Result<Cow<'_, [f32]>, ArrayRetrievalError> {
237        if let Some(a) = self.arrays.get(&ArrayType::IntensityArray) {
238            a.to_f32()
239        } else {
240            Err(ArrayRetrievalError::NotFound(ArrayType::IntensityArray))
241        }
242    }
243
244    pub fn apex_time(&self) -> Option<f64> {
245        TimeInterval::apex_time(&self)
246    }
247
248    pub fn area(&self) -> f32 {
249        TimeInterval::area(&self)
250    }
251}
252
253impl ChromatogramLike for Chromatogram {
254    fn description(&self) -> &ChromatogramDescription {
255        &self.description
256    }
257
258    fn time(&self) -> Result<Cow<'_, [f64]>, ArrayRetrievalError> {
259        self.time()
260    }
261
262    fn intensity(&self) -> Result<Cow<'_, [f32]>, ArrayRetrievalError> {
263        self.intensity()
264    }
265
266    fn description_mut(&mut self) -> &mut ChromatogramDescription {
267        &mut self.description
268    }
269}
270
271impl ParamDescribed for Chromatogram {
272    fn params(&self) -> &[Param] {
273        self.description.params()
274    }
275
276    fn params_mut(&mut self) -> &mut mzdata_param::ParamList {
277        self.description.params_mut()
278    }
279}