1use std::{borrow::Cow, convert::TryFrom};
2
3use mzpeaks::{
4 coordinate::{IonMobility, MZ, Mass},
5 feature::{ChargedFeature, Feature, FeatureLike},
6 feature_map::FeatureMap,
7 prelude::*,
8};
9
10use crate::scan_properties::SCAN_TITLE;
11use crate::{
12 Acquisition, MultiLayerSpectrum, Precursor,
13 RawSpectrum, ScanPolarity, SignalContinuity, SpectrumConversionError, SpectrumDescription,
14};
15use mzdata_bindata::{
16 ArrayRetrievalError, ArrayType, ArraysAvailable, BinaryArrayMap, BinaryArrayMap3D,
17 BuildArrayMap3DFrom,
18 BuildFromArrayMap,
19 BuildFromArrayMap3D,
20 ByteArrayViewMut,
21};
22use mzdata_param::{
23 Param, ParamDescribed, ParamList, ParamValue, Unit, impl_param_described,
24};
25
26#[derive(Debug)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum FeatureDataLevel<
34 C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
35 D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
36> {
37 Missing,
39 RawData(BinaryArrayMap3D),
41 Centroid(FeatureMap<MZ, IonMobility, C>),
43 Deconvoluted(FeatureMap<Mass, IonMobility, D>),
45}
46
47#[derive(Debug)]
50pub enum RefFeatureDataLevel<
51 'a,
52 C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
53 D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
54> {
55 Missing,
57 RawData(&'a BinaryArrayMap3D),
59 Centroid(&'a FeatureMap<MZ, IonMobility, C>),
61 Deconvoluted(&'a FeatureMap<Mass, IonMobility, D>),
63}
64
65#[derive(Debug, Default, Clone)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct IonMobilityFrameDescription {
73 pub id: String,
75
76 pub index: usize,
78
79 pub ms_level: u8,
81
82 pub polarity: ScanPolarity,
84
85 pub signal_continuity: SignalContinuity,
88
89 pub params: ParamList,
92
93 pub acquisition: Acquisition,
95
96 pub precursor: Vec<Precursor>,
98
99 pub ion_mobility_unit: Unit,
101}
102
103impl_param_described!(IonMobilityFrameDescription);
104
105impl IonMobilityFrameDescription {
106 #[allow(clippy::too_many_arguments)]
107 pub fn new(
108 id: String,
109 index: usize,
110 ms_level: u8,
111 polarity: ScanPolarity,
112 signal_continuity: SignalContinuity,
113 params: ParamList,
114 acquisition: Acquisition,
115 precursor: impl Into<super::AsPrecursorCollection>,
116 ion_mobility_unit: Unit,
117 ) -> Self {
118 Self {
119 id,
120 index,
121 ms_level,
122 polarity,
123 signal_continuity,
124 params,
125 acquisition,
126 precursor: precursor.into().into(),
127 ion_mobility_unit,
128 }
129 }
130
131 pub fn title(&self) -> Option<Cow<'_, str>> {
132 self.get_param_by_curie(&SCAN_TITLE).map(|p| p.as_str())
133 }
134}
135
136impl From<SpectrumDescription> for IonMobilityFrameDescription {
137 fn from(value: SpectrumDescription) -> Self {
138 Self::new(
139 value.id,
140 value.index,
141 value.ms_level,
142 value.polarity,
143 value.signal_continuity,
144 value.params,
145 value.acquisition,
146 value.precursor,
147 Unit::Unknown,
148 )
149 }
150}
151
152impl From<IonMobilityFrameDescription> for SpectrumDescription {
153 fn from(value: IonMobilityFrameDescription) -> Self {
154 Self::new(
155 value.id,
156 value.index,
157 value.ms_level,
158 value.polarity,
159 value.signal_continuity,
160 value.params,
161 value.acquisition,
162 value.precursor,
163 )
164 }
165}
166
167pub trait IonMobilityFrameLike<
172 C: FeatureLike<MZ, IonMobility>,
173 D: FeatureLike<Mass, IonMobility> + KnownCharge,
174>: ParamDescribed
175{
176 fn description(&self) -> &IonMobilityFrameDescription;
179
180 fn description_mut(&mut self) -> &mut IonMobilityFrameDescription;
182
183 fn ion_mobility_unit(&self) -> Unit {
185 let unit = self.description().ion_mobility_unit;
186 if unit == Unit::Unknown {
187 self.raw_arrays()
188 .map(|a| a.ion_mobility_unit)
189 .unwrap_or_default()
190 } else {
191 unit
192 }
193 }
194
195 fn ion_mobility_unit_mut(&mut self) -> &mut Unit {
197 if self.description().ion_mobility_unit == Unit::Unknown {
198 if let Some(unit) = self.raw_arrays().map(|a| a.ion_mobility_unit) {
199 self.description_mut().ion_mobility_unit = unit;
200 }
201 }
202 &mut self.description_mut().ion_mobility_unit
203 }
204
205 #[inline]
207 fn acquisition(&self) -> &Acquisition {
208 &self.description().acquisition
209 }
210
211 #[inline]
213 fn precursor(&self) -> Option<&Precursor> {
214 let desc = self.description();
215 if let Some(precursor) = &desc.precursor.first() {
216 Some(precursor)
217 } else {
218 None
219 }
220 }
221
222 fn precursor_iter(&self) -> impl Iterator<Item = &Precursor> + ExactSizeIterator {
224 let desc = self.description();
225 desc.precursor.iter()
226 }
227
228 fn precursor_count(&self) -> usize {
230 self.description().precursor.len()
231 }
232
233 fn precursor_mut(&mut self) -> Option<&mut Precursor> {
235 let desc = self.description_mut();
236 if let Some(precursor) = desc.precursor.first_mut() {
237 Some(precursor)
238 } else {
239 None
240 }
241 }
242
243 fn precursor_iter_mut(&mut self) -> impl Iterator<Item = &mut Precursor> + ExactSizeIterator {
245 let desc = self.description_mut();
246 desc.precursor.iter_mut()
247 }
248
249 fn add_precursor(&mut self, precursor: Precursor) {
257 self.description_mut().precursor.push(precursor);
258 }
259
260 fn remove_precursor(&mut self, index: usize) -> Precursor {
266 self.description_mut().precursor.remove(index)
267 }
268
269 #[inline]
271 fn start_time(&self) -> f64 {
272 let acq = self.acquisition();
273 match acq.scans.first() {
274 Some(evt) => evt.start_time,
275 None => 0.0,
276 }
277 }
278
279 #[inline]
281 fn ms_level(&self) -> u8 {
282 self.description().ms_level
283 }
284
285 #[inline]
287 fn id(&self) -> &str {
288 &self.description().id
289 }
290
291 #[inline]
293 fn index(&self) -> usize {
294 self.description().index
295 }
296
297 #[inline]
303 fn signal_continuity(&self) -> SignalContinuity {
304 self.description().signal_continuity
305 }
306
307 #[inline]
309 fn polarity(&self) -> ScanPolarity {
310 self.description().polarity
311 }
312
313 fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap3D>;
317
318 fn features(&self) -> RefFeatureDataLevel<'_, C, D>;
320
321 fn into_features_and_parts(self) -> (FeatureDataLevel<C, D>, IonMobilityFrameDescription);
328}
329
330#[derive(Debug, Default, Clone)]
339#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
340pub struct MultiLayerIonMobilityFrame<
341 C: FeatureLike<MZ, IonMobility> = Feature<MZ, IonMobility>,
342 D: FeatureLike<Mass, IonMobility> + KnownCharge = ChargedFeature<Mass, IonMobility>,
343> {
344 pub arrays: Option<BinaryArrayMap3D>,
346 pub features: Option<FeatureMap<MZ, IonMobility, C>>,
348 pub deconvoluted_features: Option<FeatureMap<Mass, IonMobility, D>>,
350 pub description: IonMobilityFrameDescription,
351}
352
353impl<
354 C: FeatureLike<MZ, IonMobility> + BuildFromArrayMap3D,
355 D: FeatureLike<Mass, IonMobility> + KnownCharge + BuildFromArrayMap3D,
356> MultiLayerIonMobilityFrame<C, D>
357{
358 pub fn try_build_features(
363 &mut self,
364 ) -> Result<RefFeatureDataLevel<'_, C, D>, SpectrumConversionError> {
365 if matches!(self.signal_continuity(), SignalContinuity::Centroid) {
366 if let Some(arrays) = self.arrays.as_ref() {
367 if let ArraysAvailable::Ok = D::has_arrays_3d_for(arrays) {
368 let peaks = D::try_from_arrays_3d(arrays).map(|f| FeatureMap::new(f))?;
369 self.deconvoluted_features = Some(peaks);
370 return Ok(self.features());
371 }
372
373 if let ArraysAvailable::Ok = C::has_arrays_3d_for(arrays) {
374 let peaks = C::try_from_arrays_3d(arrays).map(|f| FeatureMap::new(f))?;
375 self.features = Some(peaks);
376 return Ok(self.features());
377 }
378 Ok(RefFeatureDataLevel::Missing)
379 } else {
380 Ok(self.features())
381 }
382 } else {
383 Ok(self.features())
384 }
385 }
386}
387
388impl<C: FeatureLike<MZ, IonMobility>, D: FeatureLike<Mass, IonMobility> + KnownCharge>
389 ParamDescribed for MultiLayerIonMobilityFrame<C, D>
390{
391 fn params(&self) -> &[Param] {
392 &self.description().params
393 }
394
395 fn params_mut(&mut self) -> &mut ParamList {
396 &mut self.description_mut().params
397 }
398}
399
400impl<C: FeatureLike<MZ, IonMobility>, D: FeatureLike<Mass, IonMobility> + KnownCharge>
401 MultiLayerIonMobilityFrame<C, D>
402{
403 pub fn new(
407 arrays: Option<BinaryArrayMap3D>,
408 features: Option<FeatureMap<MZ, IonMobility, C>>,
409 deconvoluted_features: Option<FeatureMap<Mass, IonMobility, D>>,
410 description: IonMobilityFrameDescription,
411 ) -> Self {
412 let mut this = Self {
413 arrays,
414 features,
415 deconvoluted_features,
416 description,
417 };
418 if this.description.ion_mobility_unit.is_unknown() {
419 if let Some(arrays) = this.raw_arrays() {
420 this.description.ion_mobility_unit = arrays.ion_mobility_unit
421 }
422 }
423 this
424 }
425}
426
427impl<C: FeatureLike<MZ, IonMobility>, D: FeatureLike<Mass, IonMobility> + KnownCharge>
428 IonMobilityFrameLike<C, D> for MultiLayerIonMobilityFrame<C, D>
429{
430 fn description(&self) -> &IonMobilityFrameDescription {
431 &self.description
432 }
433
434 fn description_mut(&mut self) -> &mut IonMobilityFrameDescription {
435 &mut self.description
436 }
437
438 fn raw_arrays(&'_ self) -> Option<&'_ BinaryArrayMap3D> {
439 self.arrays.as_ref()
440 }
441
442 fn features(&self) -> RefFeatureDataLevel<'_, C, D> {
443 let state = if let Some(d) = self.deconvoluted_features.as_ref() {
444 RefFeatureDataLevel::Deconvoluted(d)
445 } else if let Some(c) = self.features.as_ref() {
446 RefFeatureDataLevel::Centroid(c)
447 } else if let Some(arrays) = self.arrays.as_ref() {
448 RefFeatureDataLevel::RawData(arrays)
449 } else {
450 RefFeatureDataLevel::Missing
451 };
452 state
453 }
454
455 fn into_features_and_parts(self) -> (FeatureDataLevel<C, D>, IonMobilityFrameDescription) {
456 let state = if let Some(d) = self.deconvoluted_features {
457 FeatureDataLevel::Deconvoluted(d)
458 } else if let Some(c) = self.features {
459 FeatureDataLevel::Centroid(c)
460 } else if let Some(arrays) = self.arrays {
461 FeatureDataLevel::RawData(arrays)
462 } else {
463 FeatureDataLevel::Missing
464 };
465
466 (state, self.description)
467 }
468}
469
470impl<CFeat: FeatureLike<MZ, IonMobility>, DFeat: FeatureLike<Mass, IonMobility> + KnownCharge>
476 TryFrom<RawSpectrum> for MultiLayerIonMobilityFrame<CFeat, DFeat>
477{
478 type Error = ArrayRetrievalError;
479
480 fn try_from(value: RawSpectrum) -> Result<Self, Self::Error> {
481 let arrays = BinaryArrayMap3D::try_from(value.arrays)?;
482 let descr = IonMobilityFrameDescription::from(value.description);
483
484 Ok(MultiLayerIonMobilityFrame::new(
485 Some(arrays),
486 None,
487 None,
488 descr,
489 ))
490 }
491}
492
493impl<
499 CFeat: FeatureLike<MZ, IonMobility>,
500 DFeat: FeatureLike<Mass, IonMobility> + KnownCharge,
501 CPeak: CentroidLike,
502 DPeak: DeconvolutedCentroidLike,
503> TryFrom<MultiLayerSpectrum<CPeak, DPeak>> for MultiLayerIonMobilityFrame<CFeat, DFeat>
504{
505 type Error = ArrayRetrievalError;
506
507 fn try_from(value: MultiLayerSpectrum<CPeak, DPeak>) -> Result<Self, Self::Error> {
508 let arrays = match value.arrays {
509 Some(arrays) => BinaryArrayMap3D::try_from(arrays)?,
510 None => return Err(ArrayRetrievalError::NotFound(ArrayType::IonMobilityArray)),
511 };
512
513 let descr = IonMobilityFrameDescription::from(value.description);
514
515 Ok(MultiLayerIonMobilityFrame::new(
516 Some(arrays),
517 None,
518 None,
519 descr,
520 ))
521 }
522}
523
524impl<
527 C: FeatureLike<MZ, IonMobility> + BuildArrayMap3DFrom,
528 D: FeatureLike<Mass, IonMobility> + KnownCharge + BuildArrayMap3DFrom,
529> TryFrom<MultiLayerIonMobilityFrame<C, D>> for RawSpectrum
530{
531 type Error = ArrayRetrievalError;
532
533 fn try_from(value: MultiLayerIonMobilityFrame<C, D>) -> Result<Self, Self::Error> {
534 let im_unit = value.ion_mobility_unit();
535 let mut arrays = if let Some(d) = value.deconvoluted_features {
536 BuildArrayMap3DFrom::as_arrays_3d(&d[..]).unstack()?
537 } else if let Some(c) = value.features {
538 BuildArrayMap3DFrom::as_arrays_3d(&c[..]).unstack()?
539 } else if let Some(arrays) = value.arrays {
540 arrays.unstack()?
541 } else {
542 BinaryArrayMap::default()
543 };
544
545 arrays.iter_mut().for_each(|(at, da)| {
546 if at.is_ion_mobility() {
547 *da.unit_mut() = im_unit;
548 }
549 });
550
551 let descr = value.description.into();
552
553 Ok(RawSpectrum::new(descr, arrays))
554 }
555}
556
557
558impl<
561 CFeat: FeatureLike<MZ, IonMobility> + BuildArrayMap3DFrom,
562 DFeat: FeatureLike<Mass, IonMobility> + KnownCharge + BuildArrayMap3DFrom,
563 CPeak: CentroidLike + BuildFromArrayMap,
564 DPeak: DeconvolutedCentroidLike + BuildFromArrayMap,
565> TryFrom<MultiLayerIonMobilityFrame<CFeat, DFeat>> for MultiLayerSpectrum<CPeak, DPeak>
566{
567 type Error = ArrayRetrievalError;
568
569 fn try_from(value: MultiLayerIonMobilityFrame<CFeat, DFeat>) -> Result<Self, Self::Error> {
570 let raw: RawSpectrum = value.try_into()?;
571 Ok(raw.into())
572 }
573}
574
575#[cfg(feature = "mzsignal")]
576mod mzsignal_impl {
577 use super::*;
578
579 use mzpeaks::{CentroidPeak, Tolerance, feature::Feature, peak_set::PeakSetVec};
580 use mzsignal::{
581 PeakFitType,
582 feature_mapping::{FeatureExtracterType, MapState, PeakMapState},
583 peak_picker::PeakPicker,
584 };
585
586 impl<
589 C: FeatureLike<MZ, IonMobility> + FeatureLikeMut<MZ, IonMobility>,
590 D: FeatureLike<Mass, IonMobility> + KnownCharge,
591 > MultiLayerIonMobilityFrame<C, D>
592 {
593 pub fn extract_features_with<
622 E: MapState<CentroidPeak, MZ, IonMobility, FeatureType = C> + Default,
623 >(
624 &mut self,
625 error_tolerance: Tolerance,
626 min_length: usize,
627 maximum_gap_size: f64,
628 peak_picker: Option<PeakPicker>,
629 ) -> Result<(), ArrayRetrievalError> {
630 if let Some(arrays) = self.arrays.as_ref() {
631 let features = match self.signal_continuity() {
632 SignalContinuity::Unknown => {
633 panic!("Cannot extract feature, unknown signal continuities")
634 }
635 SignalContinuity::Centroid => {
636 let mut extractor: FeatureExtracterType<E, _, _, IonMobility> = arrays
637 .iter()
638 .flat_map(
639 |(time, submap)| -> Result<
640 (f64, PeakSetVec<CentroidPeak, MZ>),
641 ArrayRetrievalError,
642 > {
643 let mzs = submap.mzs()?;
644 let intens = submap.intensities()?;
645 let peaks = mzs
646 .iter()
647 .zip(intens.iter())
648 .map(|(mz, inten)| CentroidPeak::new(*mz, *inten, 0))
649 .collect();
650 Ok((time, peaks))
651 },
652 )
653 .collect();
654 extractor.extract_features(error_tolerance, min_length, maximum_gap_size)
655 }
656 SignalContinuity::Profile => {
657 let peak_picker = peak_picker.unwrap_or_else(|| {
658 PeakPicker::new(1.0, 1.0, 1.0, PeakFitType::Quadratic)
659 });
660 let mut extractor: FeatureExtracterType<E, _, _, IonMobility> = arrays
661 .iter()
662 .flat_map(
663 |(time, submap)| -> Result<
664 (f64, PeakSetVec<CentroidPeak, MZ>),
665 ArrayRetrievalError,
666 > {
667 let mzs = submap.mzs()?;
668 let intens = submap.intensities()?;
669 let mut peaks = Vec::new();
670 peak_picker
671 .discover_peaks(&mzs, &intens, &mut peaks)
672 .unwrap();
673 let peaks =
674 peaks.into_iter().map(|p| p.as_centroid()).collect();
675 Ok((time, peaks))
676 },
677 )
678 .collect();
679 extractor.extract_features(error_tolerance, min_length, maximum_gap_size)
680 }
681 };
682 self.features = Some(features);
683 }
684 Ok(())
685 }
686 }
687
688 impl<D: FeatureLike<Mass, IonMobility> + KnownCharge>
691 MultiLayerIonMobilityFrame<Feature<MZ, IonMobility>, D>
692 {
693 pub fn extract_features_simple(
709 &mut self,
710 error_tolerance: Tolerance,
711 min_length: usize,
712 maximum_gap_size: f64,
713 peak_picker: Option<PeakPicker>,
714 ) -> Result<(), ArrayRetrievalError> {
715 self.extract_features_with::<PeakMapState<CentroidPeak, MZ>>(
716 error_tolerance,
717 min_length,
718 maximum_gap_size,
719 peak_picker,
720 )
721 }
722 }
723}