Skip to main content

mzdata_bindata/
conversion.rs

1use std::{collections::HashSet, convert::TryInto, mem};
2
3use mzpeaks::{
4    CentroidLike, CentroidPeak, CoordinateLike, DeconvolutedPeak, DeconvolutedPeakSet, IonMobility,
5    MZ, MZPeakSetType, Mass, PeakSet,
6    feature::{ChargedFeature, Feature},
7    peak::{IonMobilityAwareCentroidPeak, IonMobilityAwareDeconvolutedPeak},
8    prelude::*,
9};
10
11use mzdata_param::Unit;
12
13use crate::utils::{mass_charge_ratio, neutral_mass};
14
15use super::{
16    BinaryArrayMap3D, ByteArrayView,
17    array::DataArray,
18    encodings::{ArrayRetrievalError, ArrayType, BinaryDataArrayType},
19    map::BinaryArrayMap,
20};
21
22impl From<&PeakSet> for BinaryArrayMap {
23    fn from(peaks: &PeakSet) -> BinaryArrayMap {
24        let mut arrays = BinaryArrayMap::new();
25
26        let mut mz_array = DataArray::from_name_type_size(
27            &ArrayType::MZArray,
28            BinaryDataArrayType::Float64,
29            peaks.len() * BinaryDataArrayType::Float64.size_of(),
30        );
31
32        let mut intensity_array = DataArray::from_name_type_size(
33            &ArrayType::IntensityArray,
34            BinaryDataArrayType::Float32,
35            peaks.len() * BinaryDataArrayType::Float32.size_of(),
36        );
37
38        for p in peaks.iter() {
39            let mz: f64 = p.coordinate();
40            let inten: f32 = p.intensity();
41
42            let raw_bytes: [u8; mem::size_of::<f64>()] = mz.to_le_bytes();
43            mz_array.data.extend(raw_bytes);
44
45            let raw_bytes: [u8; mem::size_of::<f32>()] = inten.to_le_bytes();
46            intensity_array.data.extend(raw_bytes);
47        }
48
49        arrays.add(mz_array);
50        arrays.add(intensity_array);
51        arrays
52    }
53}
54
55impl<C: CentroidLike + From<CentroidPeak>> From<BinaryArrayMap> for MZPeakSetType<C> {
56    fn from(arrays: BinaryArrayMap) -> MZPeakSetType<C> {
57        (&arrays).into()
58    }
59}
60
61impl<C: CentroidLike + From<CentroidPeak>> From<&BinaryArrayMap> for MZPeakSetType<C> {
62    fn from(arrays: &BinaryArrayMap) -> MZPeakSetType<C> {
63        let mz_array = arrays.mzs().unwrap();
64        let intensity_array = arrays.intensities().unwrap();
65        let mut peaks = Vec::with_capacity(mz_array.len());
66
67        for (i, (mz, intensity)) in mz_array.iter().zip(intensity_array.iter()).enumerate() {
68            peaks.push(
69                CentroidPeak {
70                    mz: *mz,
71                    intensity: *intensity,
72                    index: i as u32,
73                }
74                .into(),
75            )
76        }
77        MZPeakSetType::<C>::new(peaks)
78    }
79}
80
81impl From<&BinaryArrayMap> for DeconvolutedPeakSet {
82    fn from(arrays: &BinaryArrayMap) -> DeconvolutedPeakSet {
83        let mz_array = arrays.mzs().unwrap();
84        let intensity_array = arrays.intensities().unwrap();
85        let charge_array = arrays
86            .charges()
87            .expect("Charge state array is required for deconvoluted peaks");
88        let mut peaks = Vec::with_capacity(mz_array.len());
89        for (i, ((mz, intensity), charge)) in mz_array
90            .iter()
91            .zip(intensity_array.iter())
92            .zip(charge_array.iter())
93            .enumerate()
94        {
95            peaks.push(DeconvolutedPeak {
96                neutral_mass: neutral_mass(*mz, *charge),
97                intensity: *intensity,
98                charge: *charge,
99                index: i as u32,
100            })
101        }
102
103        DeconvolutedPeakSet::new(peaks)
104    }
105}
106
107impl From<&DeconvolutedPeakSet> for BinaryArrayMap {
108    fn from(peaks: &DeconvolutedPeakSet) -> BinaryArrayMap {
109        let mut arrays = BinaryArrayMap::new();
110
111        let mut mz_array = DataArray::from_name_type_size(
112            &ArrayType::MZArray,
113            BinaryDataArrayType::Float64,
114            peaks.len() * BinaryDataArrayType::Float64.size_of(),
115        );
116
117        let mut intensity_array = DataArray::from_name_type_size(
118            &ArrayType::IntensityArray,
119            BinaryDataArrayType::Float32,
120            peaks.len() * BinaryDataArrayType::Float32.size_of(),
121        );
122
123        let mut charge_array = DataArray::from_name_type_size(
124            &ArrayType::ChargeArray,
125            BinaryDataArrayType::Int32,
126            peaks.len() * BinaryDataArrayType::Int32.size_of(),
127        );
128
129        for p in peaks.iter() {
130            let mz: f64 = p.mz();
131            let inten: f32 = p.intensity();
132            let charge = p.charge();
133
134            let raw_bytes: [u8; mem::size_of::<f64>()] = mz.to_le_bytes();
135            mz_array.data.extend_from_slice(&raw_bytes);
136
137            let raw_bytes: [u8; mem::size_of::<f32>()] = inten.to_le_bytes();
138            intensity_array.data.extend_from_slice(&raw_bytes);
139
140            let raw_bytes: [u8; mem::size_of::<i32>()] = charge.to_le_bytes();
141            charge_array.data.extend_from_slice(&raw_bytes);
142        }
143
144        arrays.add(mz_array);
145        arrays.add(intensity_array);
146        arrays.add(charge_array);
147        arrays
148    }
149}
150
151/// Whether or not sufficient arrays are available in a [`BinaryArrayMap`] to
152/// construct a peak list using [`BuildFromArrayMap`] or [`BuildFromArrayMap3D`]
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub enum ArraysAvailable {
155    /// The required arrays were not listed, so cannot say yes or no
156    Unknown,
157    /// All the required arrays are present
158    Ok,
159    /// The list off arrays that are missing
160    MissingArrays(Vec<ArrayType>),
161}
162
163/// A trait for reconstructing a `Vec` of peaks or features from a [`BinaryArrayMap`].
164///
165/// This is the inverse of [`BuildArrayMapFrom`].
166///
167/// # Examples
168/// ```
169/// use mzpeaks::CentroidPeak;
170/// use mzdata_bindata::{ArraysAvailable, BinaryArrayMap, BuildFromArrayMap};
171///
172/// let empty = BinaryArrayMap::new();
173/// assert!(matches!(
174///     CentroidPeak::has_arrays_for(&empty),
175///     ArraysAvailable::MissingArrays(_)
176/// ));
177/// ```
178pub trait BuildFromArrayMap: Sized {
179    /// The arrays that are *required* to reconstruct this type
180    ///
181    /// The default implementation returns `None`.
182    fn arrays_required() -> Option<Vec<ArrayType>> {
183        None
184    }
185
186    /// Try to build a [`Vec`] of [`Self`] from a [`BinaryArrayMap`].
187    ///
188    /// If arrays are absent or malformed, returns [`ArrayRetrievalError`]
189    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError>;
190
191    /// A shortcut form of [`Self::try_from_arrays`] that panics if it fails
192    fn from_arrays(arrays: &BinaryArrayMap) -> Vec<Self> {
193        Self::try_from_arrays(arrays).unwrap()
194    }
195
196    /// A pre-emptive check for the presence of the required arrays.
197    fn has_arrays_for(arrays: &BinaryArrayMap) -> ArraysAvailable {
198        if let Some(arrays_required) = Self::arrays_required() {
199            let missing: Vec<_> = arrays_required
200                .into_iter()
201                .filter(|array_type| !arrays.has_array(array_type))
202                .collect();
203            if !missing.is_empty() {
204                ArraysAvailable::MissingArrays(missing)
205            } else {
206                ArraysAvailable::Ok
207            }
208        } else {
209            ArraysAvailable::Unknown
210        }
211    }
212}
213
214/// A trait for converting a slice of peaks or features into a [`BinaryArrayMap`].
215///
216/// This is the inverse of [`BuildFromArrayMap`].
217///
218/// # Examples
219/// ```
220/// use mzpeaks::CentroidPeak;
221/// use mzdata_bindata::{ArrayType, BuildArrayMapFrom, BuildFromArrayMap};
222///
223/// let peaks = vec![
224///     CentroidPeak::new(500.0, 1200.0, 0),
225///     CentroidPeak::new(750.5, 800.0, 1),
226/// ];
227///
228/// let arrays = CentroidPeak::as_arrays(&peaks);
229/// assert!(arrays.has_array(&ArrayType::MZArray));
230/// assert!(arrays.has_array(&ArrayType::IntensityArray));
231///
232/// let roundtripped = CentroidPeak::try_from_arrays(&arrays).unwrap();
233/// assert_eq!(roundtripped.len(), 2);
234/// assert_eq!(roundtripped[0].mz, 500.0);
235/// ```
236pub trait BuildArrayMapFrom: Sized {
237    /// The [`ArrayType`] produced by this peak type when converted.
238    ///
239    /// The default implementation returns `None`.
240    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
241        None
242    }
243
244    /// Serialize a slice of [`Self`] into a [`BinaryArrayMap`].
245    ///
246    /// This assumes that the operation cannot fail as all the data
247    /// is available in memory already.
248    fn as_arrays(source: &[Self]) -> BinaryArrayMap;
249}
250
251/// An extension of [`BuildArrayMapFrom`] that produces [`BinaryArrayMap3D`]
252/// instead.
253///
254/// It is intended for use with ion mobility frames like [`MultiLayerIonMobilityFrame`](mzdata::MultiLayerIonMobilityFrame)
255/// and feature types like [`Feature`](mzpeaks::feature::Feature).
256pub trait BuildArrayMap3DFrom: BuildArrayMapFrom {
257    /// Serialize a slice of [`Self`] into a [`BinaryArrayMap3D`].
258    ///
259    /// The default implementation just calls [`BuildArrayMapFrom::as_arrays`] and
260    /// then reshapes them into a [`BinaryArrayMap3D`]. This leverages the requirement
261    /// that a flat [`BinaryArrayMap`] can already be built from the same type, but may
262    /// be more expensive than expected because it then makes copies of all of the data
263    /// to re-arrange in a 3D layout.
264    ///
265    /// # Panics
266    /// The default implementation panics if the intermediate [`BinaryArrayMap`] cannot
267    /// actually be converted into a [`BinaryArrayMap3D`]
268    fn as_arrays_3d(source: &[Self]) -> BinaryArrayMap3D {
269        BuildArrayMapFrom::as_arrays(source).try_into().unwrap()
270    }
271}
272
273/// An extension of [`BuildFromArrayMap`] that consumes [`BinaryArray3D`]
274/// to build a [`Vec`] of [`Self`].
275///
276/// It is intended for use with ion mobility frames like [`MultiLayerIonMobilityFrame`](mzdata::MultiLayerIonMobilityFrame)
277/// and feature types like [`Feature`](mzpeaks::feature::Feature).
278pub trait BuildFromArrayMap3D: BuildFromArrayMap {
279    /// Try to consume a [`BinaryArrayMap3D`] to build a `Vec` of [`Self`].
280    ///
281    /// The default implementation calls [`BinaryArrayMap3D::unstack`] and tries
282    /// to use [`BuildFromArrayMap::try_from_arrays`]. A direct implementation
283    /// would be much more efficient.
284    fn try_from_arrays_3d(arrays: &BinaryArrayMap3D) -> Result<Vec<Self>, ArrayRetrievalError> {
285        BuildFromArrayMap::try_from_arrays(&arrays.unstack()?)
286    }
287
288    /// A shortcut form of [`Self::try_from_arrays_3d`] that panics if it fails
289    fn from_arrays_3d(arrays: &BinaryArrayMap3D) -> Vec<Self> {
290        Self::try_from_arrays_3d(arrays).unwrap()
291    }
292
293    /// A pre-emptive check for the presence of the required arrays, particularly
294    /// across all ion mobility points.
295    fn has_arrays_3d_for(arrays: &BinaryArrayMap3D) -> ArraysAvailable {
296        if let Some(arrays_required) = Self::arrays_required() {
297            let arrays_required: Vec<_> = arrays_required
298                .into_iter()
299                .filter(|a| !a.is_ion_mobility())
300                .collect();
301            let mut arrays_not_seen: HashSet<_> = arrays_required.iter().cloned().collect();
302            let mut missing = Vec::new();
303            for (_, arr) in arrays.iter() {
304                if arr.is_empty() {
305                    continue;
306                }
307                missing.clear();
308                for array_type in arrays_required.iter() {
309                    if arr.has_array(array_type) {
310                        arrays_not_seen.remove(array_type);
311                    } else {
312                        missing.push(array_type);
313                    }
314                }
315                if missing.is_empty() {
316                    return ArraysAvailable::Ok;
317                }
318            }
319            if arrays_not_seen.is_empty() {
320                ArraysAvailable::Unknown
321            } else {
322                ArraysAvailable::MissingArrays(arrays_not_seen.into_iter().collect())
323            }
324        } else {
325            ArraysAvailable::Unknown
326        }
327    }
328}
329
330// Basic peaks
331
332/// The basic [`CentroidPeak`] implements [`BuildArrayMapFrom`]
333impl BuildArrayMapFrom for CentroidPeak {
334    /// [`CentroidPeak`] produces [`ArrayType::MZArray`] and [`ArrayType::IntensityArray`]
335    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
336        Some(vec![ArrayType::MZArray, ArrayType::IntensityArray])
337    }
338
339    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
340        let mut arrays = BinaryArrayMap::new();
341
342        let mut mz_array = DataArray::from_name_type_size(
343            &ArrayType::MZArray,
344            BinaryDataArrayType::Float64,
345            source.len() * BinaryDataArrayType::Float64.size_of(),
346        );
347        mz_array.unit = Unit::MZ;
348
349        let mut intensity_array = DataArray::from_name_type_size(
350            &ArrayType::IntensityArray,
351            BinaryDataArrayType::Float32,
352            source.len() * BinaryDataArrayType::Float32.size_of(),
353        );
354        intensity_array.unit = Unit::DetectorCounts;
355
356        for p in source.iter() {
357            let mz: f64 = p.coordinate();
358            let inten: f32 = p.intensity();
359
360            let raw_bytes: [u8; mem::size_of::<f64>()] = mz.to_le_bytes();
361            mz_array.data.extend_from_slice(&raw_bytes);
362
363            let raw_bytes: [u8; mem::size_of::<f32>()] = inten.to_le_bytes();
364            intensity_array.data.extend_from_slice(&raw_bytes);
365        }
366
367        arrays.add(mz_array);
368        arrays.add(intensity_array);
369        arrays
370    }
371}
372
373impl BuildFromArrayMap for CentroidPeak {
374    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
375        let mz_array = arrays.mzs()?;
376        let intensity_array = arrays.intensities()?;
377        let mut peaks = Vec::with_capacity(mz_array.len());
378
379        for (i, (mz, intensity)) in mz_array.iter().zip(intensity_array.iter()).enumerate() {
380            peaks.push(CentroidPeak {
381                mz: *mz,
382                intensity: *intensity,
383                index: i as u32,
384            })
385        }
386        Ok(peaks)
387    }
388
389    /// [`CentroidPeak`] requires [`ArrayType::MZArray`] and [`ArrayType::IntensityArray`]
390    fn arrays_required() -> Option<Vec<ArrayType>> {
391        Some(vec![ArrayType::MZArray, ArrayType::IntensityArray])
392    }
393}
394
395/// The basic [`DeconvolutedPeak`] implements [`BuildArrayMapFrom`].
396///
397/// It produces the much more common [`ArrayType::MZArray`] instead of
398/// [`ArrayType::MassArray`] for compatibility with other tools, and assumes
399/// that the charge carrier is a proton.
400impl BuildArrayMapFrom for DeconvolutedPeak {
401    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
402        let mut arrays = BinaryArrayMap::new();
403
404        let mut mz_array = DataArray::from_name_type_size(
405            &ArrayType::MZArray,
406            BinaryDataArrayType::Float64,
407            source.len() * BinaryDataArrayType::Float64.size_of(),
408        );
409        mz_array.unit = Unit::MZ;
410
411        let mut intensity_array = DataArray::from_name_type_size(
412            &ArrayType::IntensityArray,
413            BinaryDataArrayType::Float32,
414            source.len() * BinaryDataArrayType::Float32.size_of(),
415        );
416        intensity_array.unit = Unit::DetectorCounts;
417
418        let mut charge_array = DataArray::from_name_type_size(
419            &ArrayType::ChargeArray,
420            BinaryDataArrayType::Int32,
421            source.len() * BinaryDataArrayType::Int32.size_of(),
422        );
423
424        for p in source.iter() {
425            let mz: f64 = p.mz();
426            let inten: f32 = p.intensity();
427            let charge = p.charge();
428
429            let raw_bytes: [u8; mem::size_of::<f64>()] = mz.to_le_bytes();
430            mz_array.data.extend_from_slice(&raw_bytes);
431
432            let raw_bytes: [u8; mem::size_of::<f32>()] = inten.to_le_bytes();
433            intensity_array.data.extend_from_slice(&raw_bytes);
434
435            let raw_bytes: [u8; mem::size_of::<i32>()] = charge.to_le_bytes();
436            charge_array.data.extend_from_slice(&raw_bytes);
437        }
438
439        arrays.add(mz_array);
440        arrays.add(intensity_array);
441        arrays.add(charge_array);
442        arrays
443    }
444
445    /// [`CentroidPeak`] produces [`ArrayType::MZArray`], [`ArrayType::IntensityArray`]
446    /// and [`ArrayType::ChargeArray`]
447    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
448        Some(vec![
449            ArrayType::MZArray,
450            ArrayType::IntensityArray,
451            ArrayType::ChargeArray,
452        ])
453    }
454}
455
456/// The basic [`DeconvolutedPeak`] implements [`BuildFromArrayMap`].
457///
458/// It assumes that the charge carrier was a proton when converting to neutral mass
459/// from [`ArrayType::MZArray`].
460impl BuildFromArrayMap for DeconvolutedPeak {
461    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
462        let mz_array = arrays.mzs()?;
463        let intensity_array = arrays.intensities()?;
464        let charge_array = arrays.charges()?;
465        let mut peaks = Vec::with_capacity(mz_array.len());
466        for (i, ((mz, intensity), charge)) in mz_array
467            .iter()
468            .zip(intensity_array.iter())
469            .zip(charge_array.iter())
470            .enumerate()
471        {
472            peaks.push(DeconvolutedPeak {
473                neutral_mass: neutral_mass(*mz, *charge),
474                intensity: *intensity,
475                charge: *charge,
476                index: i as u32,
477            })
478        }
479
480        Ok(peaks)
481    }
482}
483
484// Ion mobility features
485
486/// [`Feature`] with an [`IonMobility`] dimension implement
487/// [`BuildArrayMapFrom`].
488///
489/// It uses a non-standard "feature identifier array" which assigns
490/// a unique integer identifier to all points belonging to the same
491/// [`Feature`].
492///
493/// It also produces [`ArrayType::RawIonMobilityArray`] which does not have
494/// an associated [`Unit`]. The caller *should* update this if context is available.
495impl BuildArrayMapFrom for Feature<MZ, IonMobility> {
496    /// [`Feature`] produces [`ArrayType::MZArray`] and [`ArrayType::IntensityArray`],
497    /// [`ArrayType::RawIonMobilityArray`], and a non-standard "feature identifier array"
498    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
499        Some(vec![
500            ArrayType::MZArray,
501            ArrayType::IntensityArray,
502            ArrayType::RawIonMobilityArray,
503            ArrayType::nonstandard("feature identifier array"),
504        ])
505    }
506
507    /// [`Feature`] uses a non-standard "feature identifier array" which assigns
508    /// a unique integer identifier to all points belonging to the same
509    /// [`Feature`].
510    ///
511    /// It also produces [`ArrayType::RawIonMobilityArray`] which does not have
512    /// an associated [`Unit`]. The caller *should* update these for consistency
513    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
514        let mut arrays = BinaryArrayMap::new();
515        let n: usize = source.iter().map(|f| f.len()).sum();
516
517        let mut mz_array = DataArray::from_name_type_size(
518            &ArrayType::MZArray,
519            BinaryDataArrayType::Float64,
520            n * BinaryDataArrayType::Float64.size_of(),
521        );
522        mz_array.unit = Unit::MZ;
523
524        let mut intensity_array = DataArray::from_name_type_size(
525            &ArrayType::IntensityArray,
526            BinaryDataArrayType::Float32,
527            n * BinaryDataArrayType::Float32.size_of(),
528        );
529        intensity_array.unit = Unit::DetectorCounts;
530
531        let mut ion_mobility_array = DataArray::from_name_type_size(
532            &ArrayType::RawIonMobilityArray,
533            BinaryDataArrayType::Float64,
534            n * BinaryDataArrayType::Float64.size_of(),
535        );
536
537        let mut marker_array = DataArray::from_name_type_size(
538            &ArrayType::nonstandard("feature identifier array"),
539            BinaryDataArrayType::Int32,
540            n * BinaryDataArrayType::Int32.size_of(),
541        );
542
543        let mut acc = Vec::with_capacity(n);
544        source.iter().enumerate().for_each(|(i, f)| {
545            f.iter()
546                .for_each(|(mz, im, inten)| acc.push((mz, im, inten, i)))
547        });
548        acc.sort_by(|(mz_a, im_a, _, key_a), (mz_b, im_b, _, key_b)| {
549            mz_a.total_cmp(mz_b)
550                .then(im_a.total_cmp(im_b))
551                .then(key_a.cmp(key_b))
552        });
553
554        for (mz, im, inten, key) in acc.iter() {
555            mz_array.data.extend_from_slice(&mz.to_le_bytes());
556            intensity_array.data.extend_from_slice(&inten.to_le_bytes());
557            ion_mobility_array.data.extend_from_slice(&im.to_le_bytes());
558            marker_array
559                .data
560                .extend_from_slice(&(*key as i32).to_le_bytes());
561        }
562
563        arrays.add(mz_array);
564        arrays.add(intensity_array);
565        arrays.add(ion_mobility_array);
566        arrays.add(marker_array);
567        arrays
568    }
569}
570
571/// [`Feature`] with an [`IonMobility`] dimension implement
572/// [`BuildFromArrayMap`].
573///
574/// It requires a non-standard "feature identifier array" which assigns
575/// a unique integer identifier to all points belonging to the same
576/// [`Feature`].
577///
578/// While it explicitly requires [`ArrayType::RawIonMobilityArray`], it will accept
579/// any raw ion mobility array type.
580impl BuildFromArrayMap for Feature<MZ, IonMobility> {
581    /// [`Feature`] requires [`ArrayType::MZArray`] and [`ArrayType::IntensityArray`],
582    /// [`ArrayType::RawIonMobilityArray`], and a non-standard "feature identifier array"
583    fn arrays_required() -> Option<Vec<ArrayType>> {
584        Self::default().arrays_included()
585    }
586
587    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
588        let mz_array = arrays.mzs()?;
589        let intensity_array = arrays.intensities()?;
590        let im_array = arrays
591            .get(&ArrayType::RawIonMobilityArray)
592            .or_else(|| arrays.get(&ArrayType::RawDriftTimeArray))
593            .or_else(|| arrays.get(&ArrayType::RawInverseReducedIonMobilityArray))
594            .ok_or(ArrayRetrievalError::NotFound(
595                ArrayType::RawIonMobilityArray,
596            ))?
597            .to_f64()?;
598
599        let array_key = ArrayType::nonstandard("feature identifier array");
600        let marker_array = arrays
601            .get(&array_key)
602            .ok_or(ArrayRetrievalError::NotFound(array_key))?
603            .to_i32()?;
604
605        let n = marker_array.iter().map(|i| *i as usize).max();
606
607        let mut features = if let Some(n) = n {
608            let mut features = Vec::with_capacity(n);
609            features.resize(n, Feature::default());
610            features
611        } else {
612            return Ok(Vec::new());
613        };
614
615        mz_array
616            .iter()
617            .zip(intensity_array.iter())
618            .zip(im_array.iter())
619            .zip(marker_array.iter())
620            .for_each(|(((mz, inten), im), key)| {
621                features[(*key) as usize].push_raw(*mz, *im, *inten);
622            });
623
624        Ok(features)
625    }
626}
627
628/// [`ChargedFeature`] with an [`IonMobility`] dimension implement
629/// [`BuildArrayMapFrom`].
630///
631/// It uses a non-standard "feature identifier array" which assigns
632/// a unique integer identifier to all points belonging to the same
633/// [`Feature`].
634///
635/// It also produces [`ArrayType::RawIonMobilityArray`] which does not have
636/// an associated [`Unit`]. The caller *should* update this if context is available.
637impl BuildArrayMapFrom for ChargedFeature<Mass, IonMobility> {
638    /// [`ChargedFeature`] produces [`ArrayType::MZArray`], [`ArrayType::ChargeArray`],
639    /// [`ArrayType::IntensityArray`], [`ArrayType::RawIonMobilityArray`], and a
640    /// non-standard "feature identifier array"
641    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
642        Some(vec![
643            ArrayType::MZArray,
644            ArrayType::IntensityArray,
645            ArrayType::RawIonMobilityArray,
646            ArrayType::ChargeArray,
647            ArrayType::nonstandard("feature identifier array"),
648        ])
649    }
650
651    /// [`ChargedFeature`] uses a non-standard "feature identifier array" which assigns
652    /// a unique integer identifier to all points belonging to the same
653    /// [`ChargedFeature`].
654    ///
655    /// Like [`DeconvolutedPeak`], [`ChargedFeature`] will compute its m/z value from
656    /// neutral mass and charge, and assumes that the charge carrier is a proton.
657    ///
658    /// It also produces [`ArrayType::RawIonMobilityArray`] which does not have
659    /// an associated [`Unit`]. The caller *should* update these for consistency
660    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
661        let mut arrays = BinaryArrayMap::new();
662        let n: usize = source.iter().map(|f| f.len()).sum();
663
664        let mut mz_array = DataArray::from_name_type_size(
665            &ArrayType::MZArray,
666            BinaryDataArrayType::Float64,
667            n * BinaryDataArrayType::Float64.size_of(),
668        );
669        mz_array.unit = Unit::MZ;
670
671        let mut intensity_array = DataArray::from_name_type_size(
672            &ArrayType::IntensityArray,
673            BinaryDataArrayType::Float32,
674            n * BinaryDataArrayType::Float32.size_of(),
675        );
676        intensity_array.unit = Unit::DetectorCounts;
677
678        let mut charge_array = DataArray::from_name_type_size(
679            &ArrayType::ChargeArray,
680            BinaryDataArrayType::Int32,
681            n * BinaryDataArrayType::Int32.size_of(),
682        );
683
684        let mut ion_mobility_array = DataArray::from_name_type_size(
685            &ArrayType::RawIonMobilityArray,
686            BinaryDataArrayType::Float64,
687            n * BinaryDataArrayType::Float64.size_of(),
688        );
689
690        let mut marker_array = DataArray::from_name_type_size(
691            &ArrayType::nonstandard("feature identifier array"),
692            BinaryDataArrayType::Int32,
693            n * BinaryDataArrayType::Int32.size_of(),
694        );
695
696        let mut acc = Vec::with_capacity(n);
697        source.iter().enumerate().for_each(|(i, f)| {
698            f.iter().for_each(|(mass, im, inten)| {
699                acc.push((mass_charge_ratio(mass, f.charge), im, inten, f.charge, i))
700            })
701        });
702        acc.sort_by(|(mz_a, im_a, _, _, key_a), (mz_b, im_b, _, _, key_b)| {
703            mz_a.total_cmp(mz_b)
704                .then(im_a.total_cmp(im_b))
705                .then(key_a.cmp(key_b))
706        });
707
708        for (mz, im, inten, charge, key) in acc.iter() {
709            mz_array.data.extend(mz.to_le_bytes());
710            intensity_array.data.extend(inten.to_le_bytes());
711            ion_mobility_array.data.extend(im.to_le_bytes());
712            charge_array.data.extend(charge.to_le_bytes());
713            marker_array.data.extend((*key as i32).to_le_bytes());
714        }
715
716        arrays.add(mz_array);
717        arrays.add(intensity_array);
718        arrays.add(ion_mobility_array);
719        arrays.add(charge_array);
720        arrays.add(marker_array);
721        arrays
722    }
723}
724
725/// [`ChargedFeature`] with an [`IonMobility`] dimension implement
726/// [`BuildFromArrayMap`].
727///
728/// It requires a non-standard "feature identifier array" which assigns
729/// a unique integer identifier to all points belonging to the same
730/// [`Feature`].
731///
732/// Like [`DeconvolutedPeak`], [`ChargedFeature`] will compute its neutral mass from
733/// m/z and assumes that the charge carrier is a proton.
734///
735/// While it explicitly requires [`ArrayType::RawIonMobilityArray`], it will accept
736/// any raw ion mobility array type.
737impl BuildFromArrayMap for ChargedFeature<Mass, IonMobility> {
738    /// [`ChargedFeature`] requires [`ArrayType::MZArray`], [`ArrayType::ChargeArray`] and [`ArrayType::IntensityArray`],
739    /// [`ArrayType::RawIonMobilityArray`], and a non-standard "feature identifier array"
740    fn arrays_required() -> Option<Vec<ArrayType>> {
741        Self::default().arrays_included()
742    }
743
744    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
745        let mz_array = arrays.mzs()?;
746        let intensity_array = arrays.intensities()?;
747        let im_array = arrays
748            .get(&ArrayType::RawIonMobilityArray)
749            .or_else(|| arrays.get(&ArrayType::RawDriftTimeArray))
750            .or_else(|| arrays.get(&ArrayType::RawInverseReducedIonMobilityArray))
751            .ok_or(ArrayRetrievalError::NotFound(
752                ArrayType::RawIonMobilityArray,
753            ))?
754            .to_f64()?;
755
756        let charge_array = arrays
757            .get(&ArrayType::ChargeArray)
758            .ok_or(ArrayRetrievalError::NotFound(ArrayType::ChargeArray))?
759            .to_i32()?;
760
761        let array_key = ArrayType::nonstandard("feature identifier array");
762        let marker_array = arrays
763            .get(&array_key)
764            .ok_or(ArrayRetrievalError::NotFound(array_key))?
765            .to_i32()?;
766
767        let n = marker_array.iter().map(|i| *i as usize).max();
768
769        let mut features = if let Some(n) = n {
770            let mut features = Vec::with_capacity(n);
771            features.resize(n, ChargedFeature::default());
772            features
773        } else {
774            return Ok(Vec::new());
775        };
776
777        mz_array
778            .iter()
779            .zip(intensity_array.iter())
780            .zip(im_array.iter().zip(charge_array.iter()))
781            .zip(marker_array.iter())
782            .for_each(|(((mz, inten), (im, charge)), key)| {
783                let f = &mut features[(*key) as usize];
784                if f.is_empty() {
785                    f.charge = *charge;
786                }
787                f.push_raw(neutral_mass(*mz, *charge), *im, *inten);
788            });
789
790        Ok(features)
791    }
792}
793
794/// [`Feature`] uses the default [`BuildArrayMap3DFrom`] implementation.
795impl BuildArrayMap3DFrom for Feature<MZ, IonMobility> {}
796
797impl BuildFromArrayMap3D for Feature<MZ, IonMobility> {
798    fn try_from_arrays_3d(arrays: &BinaryArrayMap3D) -> Result<Vec<Self>, ArrayRetrievalError> {
799        let key = ArrayType::nonstandard("feature identifier array");
800        let mut n: usize = 0;
801        for (_, arr) in arrays.iter() {
802            if arr.is_empty() {
803                continue;
804            }
805            if let Some(arr) = arr.get(&key) {
806                if let Some(i) = arr.iter_i32()?.map(|i| i as usize).max() {
807                    n = n.max(i);
808                }
809            }
810        }
811
812        if n == 0 {
813            return Ok(Vec::new());
814        }
815        n += 1;
816        let mut index = Vec::with_capacity(n);
817        index.resize(n, Feature::default());
818
819        for (im, arr) in arrays.iter() {
820            if arr.is_empty() {
821                continue;
822            }
823
824            let mz_array = arr.mzs()?;
825            let intensity_array = arr.intensities()?;
826            let marker_array = arr
827                .get(&key)
828                .ok_or_else(|| ArrayRetrievalError::NotFound(key.clone()))?
829                .to_i32()?;
830
831            for ((mz, inten), key_i) in mz_array
832                .iter()
833                .zip(intensity_array.iter())
834                .zip(marker_array.iter())
835            {
836                index[(*key_i) as usize].push_raw(*mz, im, *inten);
837            }
838        }
839
840        Ok(index)
841    }
842}
843
844/// [`ChargedFeature`] uses the default [`BuildArrayMap3DFrom`] implementation.
845impl BuildArrayMap3DFrom for ChargedFeature<Mass, IonMobility> {}
846
847impl BuildFromArrayMap3D for ChargedFeature<Mass, IonMobility> {
848    fn try_from_arrays_3d(arrays: &BinaryArrayMap3D) -> Result<Vec<Self>, ArrayRetrievalError> {
849        let key = ArrayType::nonstandard("feature identifier array");
850        let mut n: usize = 0;
851        for (_, arr) in arrays.iter() {
852            if arr.is_empty() {
853                continue;
854            }
855            if let Some(arr) = arr.get(&key) {
856                if let Some(i) = arr.iter_i32()?.map(|i| i as usize).max() {
857                    n = n.max(i);
858                }
859            }
860        }
861
862        if n == 0 {
863            return Ok(Vec::new());
864        }
865        n += 1;
866        let mut index = Vec::with_capacity(n);
867        index.resize(n, ChargedFeature::default());
868
869        for (im, arr) in arrays.iter() {
870            if arr.is_empty() {
871                continue;
872            }
873
874            let mz_array = arr.mzs()?;
875            let intensity_array = arr.intensities()?;
876            let marker_array = arr
877                .get(&key)
878                .ok_or_else(|| ArrayRetrievalError::NotFound(key.clone()))?
879                .to_i32()?;
880            let charge_array = arr.charges()?;
881
882            for ((mz, inten), (charge, key_i)) in mz_array
883                .iter()
884                .zip(intensity_array.iter())
885                .zip(charge_array.iter().zip(marker_array.iter()))
886            {
887                let f = &mut index[(*key_i) as usize];
888                f.push_raw(*mz, im, *inten);
889                f.charge = *charge;
890            }
891        }
892
893        Ok(index)
894    }
895}
896
897// Ion mobility-aware peaks
898
899/// TODO
900impl BuildArrayMapFrom for IonMobilityAwareCentroidPeak {
901    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
902        Some(vec![
903            ArrayType::MZArray,
904            ArrayType::IntensityArray,
905            ArrayType::MeanIonMobilityArray,
906        ])
907    }
908
909    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
910        let mut arrays = BinaryArrayMap::new();
911
912        let mut mz_array = DataArray::from_name_type_size(
913            &ArrayType::MZArray,
914            BinaryDataArrayType::Float64,
915            source.len() * BinaryDataArrayType::Float64.size_of(),
916        );
917        mz_array.unit = Unit::MZ;
918
919        let mut intensity_array = DataArray::from_name_type_size(
920            &ArrayType::IntensityArray,
921            BinaryDataArrayType::Float32,
922            source.len() * BinaryDataArrayType::Float32.size_of(),
923        );
924        intensity_array.unit = Unit::DetectorCounts;
925
926        let mut im_array = DataArray::from_name_type_size(
927            &ArrayType::MeanIonMobilityArray,
928            BinaryDataArrayType::Float64,
929            source.len() * BinaryDataArrayType::Float64.size_of(),
930        );
931
932        for p in source.iter() {
933            let mz: f64 = p.mz();
934            let inten: f32 = p.intensity();
935            let im = p.ion_mobility();
936
937            let raw_bytes: [u8; mem::size_of::<f64>()] = mz.to_le_bytes();
938            mz_array.data.extend_from_slice(&raw_bytes);
939
940            let raw_bytes: [u8; mem::size_of::<f32>()] = inten.to_le_bytes();
941            intensity_array.data.extend_from_slice(&raw_bytes);
942
943            let raw_bytes: [u8; mem::size_of::<f64>()] = im.to_le_bytes();
944            im_array.data.extend_from_slice(&raw_bytes);
945        }
946
947        arrays.add(mz_array);
948        arrays.add(intensity_array);
949        arrays.add(im_array);
950        arrays
951    }
952}
953
954/// TODO
955impl BuildFromArrayMap for IonMobilityAwareCentroidPeak {
956    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
957        let mz_array = arrays.mzs()?;
958        let intensity_array = arrays.intensities()?;
959        let (im_array, _) = arrays.ion_mobility()?;
960        let mut peaks = Vec::with_capacity(mz_array.len());
961
962        for (i, (mz, (intensity, ion_mobility))) in mz_array
963            .iter()
964            .zip(intensity_array.iter().zip(im_array.iter()))
965            .enumerate()
966        {
967            peaks.push(IonMobilityAwareCentroidPeak {
968                mz: *mz,
969                intensity: *intensity,
970                index: i as u32,
971                ion_mobility: *ion_mobility,
972            })
973        }
974        Ok(peaks)
975    }
976
977    fn arrays_required() -> Option<Vec<ArrayType>> {
978        Some(vec![
979            ArrayType::MZArray,
980            ArrayType::IntensityArray,
981            ArrayType::IonMobilityArray,
982        ])
983    }
984}
985
986/// TODO
987impl BuildArrayMapFrom for IonMobilityAwareDeconvolutedPeak {
988    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
989        Some(vec![
990            ArrayType::MZArray,
991            ArrayType::IntensityArray,
992            ArrayType::ChargeArray,
993            ArrayType::MeanIonMobilityArray,
994        ])
995    }
996
997    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
998        let mut arrays = BinaryArrayMap::new();
999
1000        let mut mz_array = DataArray::from_name_type_size(
1001            &ArrayType::MZArray,
1002            BinaryDataArrayType::Float64,
1003            source.len() * BinaryDataArrayType::Float64.size_of(),
1004        );
1005        mz_array.unit = Unit::MZ;
1006
1007        let mut intensity_array = DataArray::from_name_type_size(
1008            &ArrayType::IntensityArray,
1009            BinaryDataArrayType::Float32,
1010            source.len() * BinaryDataArrayType::Float32.size_of(),
1011        );
1012        intensity_array.unit = Unit::DetectorCounts;
1013
1014        let mut charge_array = DataArray::from_name_type_size(
1015            &ArrayType::ChargeArray,
1016            BinaryDataArrayType::Int32,
1017            source.len() * BinaryDataArrayType::Int32.size_of(),
1018        );
1019
1020        let mut im_array = DataArray::from_name_type_size(
1021            &ArrayType::MeanIonMobilityArray,
1022            BinaryDataArrayType::Float64,
1023            source.len() * BinaryDataArrayType::Float64.size_of(),
1024        );
1025
1026        for p in source.iter() {
1027            let mz: f64 = p.mz();
1028            let inten: f32 = p.intensity();
1029            let im = p.ion_mobility();
1030
1031            mz_array.data.extend_from_slice(&mz.to_le_bytes());
1032            intensity_array.data.extend_from_slice(&inten.to_le_bytes());
1033            im_array.data.extend_from_slice(&im.to_le_bytes());
1034            charge_array
1035                .data
1036                .extend_from_slice(&p.charge().to_le_bytes());
1037        }
1038
1039        arrays.add(mz_array);
1040        arrays.add(intensity_array);
1041        arrays.add(im_array);
1042        arrays.add(charge_array);
1043        arrays
1044    }
1045}
1046
1047/// TODO
1048impl BuildFromArrayMap for IonMobilityAwareDeconvolutedPeak {
1049    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
1050        let mz_array = arrays.mzs()?;
1051        let intensity_array = arrays.intensities()?;
1052        let charge_array = arrays.charges()?;
1053        let (im_array, _) = arrays.ion_mobility()?;
1054        let mut peaks = Vec::with_capacity(mz_array.len());
1055
1056        for (i, (mz, (intensity, (ion_mobility, charge)))) in mz_array
1057            .iter()
1058            .zip(
1059                intensity_array
1060                    .iter()
1061                    .zip(im_array.iter().zip(charge_array.iter())),
1062            )
1063            .enumerate()
1064        {
1065            let mass = neutral_mass(*mz, *charge);
1066            peaks.push(IonMobilityAwareDeconvolutedPeak {
1067                neutral_mass: mass,
1068                intensity: *intensity,
1069                index: i as u32,
1070                ion_mobility: *ion_mobility,
1071                charge: *charge,
1072            })
1073        }
1074        Ok(peaks)
1075    }
1076
1077    fn arrays_required() -> Option<Vec<ArrayType>> {
1078        Some(vec![
1079            ArrayType::MZArray,
1080            ArrayType::IntensityArray,
1081            ArrayType::ChargeArray,
1082            ArrayType::IonMobilityArray,
1083        ])
1084    }
1085}
1086
1087#[cfg(feature = "mzsignal")]
1088/// [`mzsignal::FittedPeak`] implements [`BuildArrayMapFrom`], which will
1089/// produce [`ArrayType::MZArray`], [`ArrayType::IntensityArray`], and
1090/// [`ArrayType::SignalToNoiseArray`]. All other properties will be lost
1091impl BuildArrayMapFrom for mzsignal::FittedPeak {
1092    fn arrays_included(&self) -> Option<Vec<ArrayType>> {
1093        Some(vec![
1094            ArrayType::MZArray,
1095            ArrayType::IntensityArray,
1096            ArrayType::SignalToNoiseArray,
1097        ])
1098    }
1099
1100    fn as_arrays(source: &[Self]) -> BinaryArrayMap {
1101        let mut arrays = BinaryArrayMap::new();
1102
1103        let mut mz_array = DataArray::from_name_type_size(
1104            &ArrayType::MZArray,
1105            BinaryDataArrayType::Float64,
1106            source.len() * BinaryDataArrayType::Float64.size_of(),
1107        );
1108        mz_array.unit = Unit::MZ;
1109
1110        let mut intensity_array = DataArray::from_name_type_size(
1111            &ArrayType::IntensityArray,
1112            BinaryDataArrayType::Float32,
1113            source.len() * BinaryDataArrayType::Float32.size_of(),
1114        );
1115        intensity_array.unit = Unit::DetectorCounts;
1116
1117        let mut snr_array = DataArray::from_name_type_size(
1118            &ArrayType::SignalToNoiseArray,
1119            BinaryDataArrayType::Float32,
1120            source.len() * BinaryDataArrayType::Float32.size_of(),
1121        );
1122
1123        for p in source.iter() {
1124            let mz: f64 = p.coordinate();
1125            let inten: f32 = p.intensity();
1126            mz_array.push(mz).unwrap();
1127            intensity_array.push(inten).unwrap();
1128            snr_array.push(p.signal_to_noise).unwrap();
1129        }
1130
1131        arrays.add(mz_array);
1132        arrays.add(intensity_array);
1133        arrays.add(snr_array);
1134        arrays
1135    }
1136}
1137
1138#[cfg(feature = "mzsignal")]
1139/// [`mzsignal::FittedPeak`] implements [`BuildFromArrayMap`], which will
1140/// use [`ArrayType::MZArray`], [`ArrayType::IntensityArray`], and
1141/// look for [`ArrayType::SignalToNoiseArray`] but will default to 0.0 if it
1142/// is absent. All other properties are forced to 0.0.
1143impl BuildFromArrayMap for mzsignal::FittedPeak {
1144    fn arrays_required() -> Option<Vec<ArrayType>> {
1145        CentroidPeak::arrays_required()
1146    }
1147
1148    fn try_from_arrays(arrays: &BinaryArrayMap) -> Result<Vec<Self>, ArrayRetrievalError> {
1149        let mzs = arrays.mzs()?;
1150        let intens = arrays.intensities()?;
1151        let snrs = arrays
1152            .get(&ArrayType::SignalToNoiseArray)
1153            .and_then(|a| a.to_f32().ok());
1154
1155        let n = mzs.len();
1156
1157        let mut out = Vec::with_capacity(n);
1158        for (i, (mz, inten)) in mzs.iter().copied().zip(intens.iter().copied()).enumerate() {
1159            out.push(mzsignal::FittedPeak::new(
1160                mz,
1161                inten,
1162                0,
1163                snrs.as_ref()
1164                    .and_then(|v| v.get(i).copied())
1165                    .unwrap_or_default(),
1166                0.0,
1167            ))
1168        }
1169        Ok(out)
1170    }
1171}