mzdata_spectrum/
chromatogram.rs1use 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#[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
90pub trait ChromatogramLike: ParamDescribed {
93 fn description(&self) -> &ChromatogramDescription;
96
97 fn description_mut(&mut self) -> &mut ChromatogramDescription;
100
101 #[inline]
103 fn precursor(&self) -> Option<&Precursor> {
104 let desc = self.description();
105 desc.precursor.first()
106 }
107
108 fn product(&self) -> Option<&Product> {
110 let desc = self.description();
111 desc.products.first()
112 }
113
114 fn precursor_iter(&self) -> impl Iterator<Item = &Precursor> {
116 let desc = self.description();
117 desc.precursor.iter()
118 }
119
120 fn precursor_mut(&mut self) -> Option<&mut Precursor> {
122 let desc = self.description_mut();
123 desc.precursor.first_mut()
124 }
125
126 fn product_mut(&mut self) -> Option<&mut Product> {
128 let desc = self.description_mut();
129 desc.products.first_mut()
130 }
131
132 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 fn add_precursor(&mut self, precursor: Precursor) {
146 self.description_mut().precursor.push(precursor);
147 }
148
149 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 #[inline]
178 fn ms_level(&self) -> Option<u8> {
179 self.description().ms_level
180 }
181
182 #[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 #[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}