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