Skip to main content

mscore/timstof/
frame.rs

1use std::fmt;
2use std::collections::BTreeMap;
3use std::fmt::{Formatter};
4use std::sync::Arc;
5use bincode::{Decode, Encode};
6use itertools;
7use itertools::izip;
8use ordered_float::OrderedFloat;
9use rand::Rng;
10use serde::{Deserialize, Serialize};
11use crate::timstof::spectrum::TimsSpectrum;
12use crate::data::spectrum::{MsType, MzSpectrum, IndexedMzSpectrum, Vectorized, ToResolution};
13use crate::simulation::annotation::{PeakAnnotation, TimsFrameAnnotated};
14use crate::timstof::vec_utils::{filter_with_mask, find_sparse_local_maxima_mask};
15
16#[derive(Clone)]
17pub struct RawTimsFrame {
18    pub frame_id: i32,
19    pub retention_time: f64,
20    pub ms_type: MsType,
21    pub scan: Vec<u32>,
22    pub tof: Vec<u32>,
23    pub intensity: Vec<f64>,
24}
25
26impl RawTimsFrame {
27    pub fn smooth(mut self, window: u32) -> Self {
28        let mut smooth_intensities: Vec<f64> = self.intensity.clone();
29        for (current_index, current_tof) in self.tof.iter().enumerate()
30        {
31            let current_intensity: f64 = self.intensity[current_index];
32            for (_next_index, next_tof) in
33                self.tof[current_index + 1..].iter().enumerate()
34            {
35                let next_index: usize = _next_index + current_index + 1;
36                let next_intensity: f64 = self.intensity[next_index];
37                if (next_tof - current_tof) <= window {
38                    smooth_intensities[current_index] += next_intensity;
39                    smooth_intensities[next_index] += current_intensity;
40                } else {
41                    break;
42                }
43            }
44        }
45        self.intensity = smooth_intensities;
46
47        self
48    }
49    pub fn centroid(mut self, window: u32) -> Self {
50        let local_maxima: Vec<bool> = find_sparse_local_maxima_mask(
51            &self.tof,
52            &self.intensity,
53            window,
54        );
55        self.tof = filter_with_mask(&self.tof, &local_maxima);
56        self.intensity = filter_with_mask(&self.intensity, &local_maxima);
57        self.scan = filter_with_mask(&self.scan, &local_maxima);
58        self
59    }
60}
61
62#[derive(Clone, Debug, Serialize, Deserialize)]
63pub struct ImsFrame {
64    pub retention_time: f64,
65    pub mobility: Arc<Vec<f64>>,
66    pub mz: Arc<Vec<f64>>,
67    pub intensity: Arc<Vec<f64>>,
68}
69
70impl Encode for ImsFrame {
71    fn encode<E: bincode::enc::Encoder>(&self, encoder: &mut E) -> Result<(), bincode::error::EncodeError> {
72        bincode::Encode::encode(&self.retention_time, encoder)?;
73        bincode::Encode::encode(&*self.mobility, encoder)?;
74        bincode::Encode::encode(&*self.mz, encoder)?;
75        bincode::Encode::encode(&*self.intensity, encoder)?;
76        Ok(())
77    }
78}
79
80impl<Context> Decode<Context> for ImsFrame {
81    fn decode<D: bincode::de::Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, bincode::error::DecodeError> {
82        let retention_time: f64 = bincode::Decode::decode(decoder)?;
83        let mobility: Vec<f64> = bincode::Decode::decode(decoder)?;
84        let mz: Vec<f64> = bincode::Decode::decode(decoder)?;
85        let intensity: Vec<f64> = bincode::Decode::decode(decoder)?;
86        Ok(ImsFrame::new(retention_time, mobility, mz, intensity))
87    }
88}
89
90impl Default for ImsFrame {
91    fn default() -> Self {
92        ImsFrame {
93            retention_time: 0.0,
94            mobility: Arc::new(Vec::new()),
95            mz: Arc::new(Vec::new()),
96            intensity: Arc::new(Vec::new()),
97        }
98    }
99}
100
101impl ImsFrame {
102    /// Creates a new `ImsFrame` instance.
103    ///
104    /// # Arguments
105    ///
106    /// * `retention_time` - The retention time in seconds.
107    /// * `mobility` - A vector of inverse ion mobilities.
108    /// * `mz` - A vector of m/z values.
109    /// * `intensity` - A vector of intensity values.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use mscore::timstof::frame::ImsFrame;
115    ///
116    /// let frame = ImsFrame::new(100.0, vec![0.1, 0.2], vec![100.5, 200.5], vec![50.0, 60.0]);
117    /// ```
118    pub fn new(retention_time: f64, mobility: Vec<f64>, mz: Vec<f64>, intensity: Vec<f64>) -> Self {
119        ImsFrame {
120            retention_time,
121            mobility: Arc::new(mobility),
122            mz: Arc::new(mz),
123            intensity: Arc::new(intensity),
124        }
125    }
126}
127
128impl fmt::Display for ImsFrame {
129    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130        write!(f, "ImsFrame(rt: {}, data points: {})", self.retention_time, self.mobility.len())
131    }
132}
133
134#[derive(Clone)]
135pub struct ImsFrameVectorized {
136    pub retention_time: f64,
137    pub mobility: Vec<f64>,
138    pub indices: Vec<i32>,
139    pub values: Vec<f64>,
140    pub resolution: i32,
141}
142
143#[derive(Clone, Debug, Serialize, Deserialize)]
144pub struct TimsFrame {
145    pub frame_id: i32,
146    pub ms_type: MsType,
147    pub scan: Vec<i32>,
148    pub tof: Vec<i32>,
149    pub ims_frame: ImsFrame,
150}
151
152impl Encode for TimsFrame {
153    fn encode<E: bincode::enc::Encoder>(&self, encoder: &mut E) -> Result<(), bincode::error::EncodeError> {
154        bincode::Encode::encode(&self.frame_id, encoder)?;
155        bincode::Encode::encode(&self.ms_type, encoder)?;
156        bincode::Encode::encode(&self.scan, encoder)?;
157        bincode::Encode::encode(&self.tof, encoder)?;
158        bincode::Encode::encode(&self.ims_frame, encoder)?;
159        Ok(())
160    }
161}
162
163impl<Context> Decode<Context> for TimsFrame {
164    fn decode<D: bincode::de::Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, bincode::error::DecodeError> {
165        let frame_id: i32 = bincode::Decode::decode(decoder)?;
166        let ms_type: MsType = bincode::Decode::decode(decoder)?;
167        let scan: Vec<i32> = bincode::Decode::decode(decoder)?;
168        let tof: Vec<i32> = bincode::Decode::decode(decoder)?;
169        let ims_frame: ImsFrame = bincode::Decode::decode(decoder)?;
170        Ok(TimsFrame { frame_id, ms_type, scan, tof, ims_frame })
171    }
172}
173
174impl Default for TimsFrame {
175    fn default() -> Self {
176        TimsFrame {
177            frame_id: 0,
178            ms_type: MsType::Unknown,
179            scan: Vec::new(),
180            tof: Vec::new(),
181            ims_frame: ImsFrame::default(),
182        }
183    }
184}
185
186impl TimsFrame {
187    /// Creates a new `TimsFrame` instance.
188    ///
189    /// # Arguments
190    ///
191    /// * `frame_id` - index of frame in TDF raw file.
192    /// * `ms_type` - The type of frame.
193    /// * `retention_time` - The retention time in seconds.
194    /// * `scan` - A vector of scan IDs.
195    /// * `mobility` - A vector of inverse ion mobilities.
196    /// * `tof` - A vector of time-of-flight values.
197    /// * `mz` - A vector of m/z values.
198    /// * `intensity` - A vector of intensity values.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use mscore::data::spectrum::MsType;
204    /// use mscore::timstof::frame::TimsFrame;
205    /// use mscore::timstof::frame::ImsFrame;
206    ///
207    /// let frame = TimsFrame::new(1, MsType::Precursor, 100.0, vec![1, 2], vec![0.1, 0.2], vec![1000, 2000], vec![100.5, 200.5], vec![50.0, 60.0]);
208    /// ```
209    pub fn new(frame_id: i32, ms_type: MsType, retention_time: f64, scan: Vec<i32>, mobility: Vec<f64>, tof: Vec<i32>, mz: Vec<f64>, intensity: Vec<f64>) -> Self {
210        TimsFrame { frame_id, ms_type, scan, tof, ims_frame: ImsFrame::new(retention_time, mobility, mz, intensity) }
211    }
212
213    ///
214    /// Convert a given TimsFrame to an ImsFrame.
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// use mscore::data::spectrum::MsType;
220    /// use mscore::timstof::frame::TimsFrame;
221    ///
222    /// let frame = TimsFrame::new(1, MsType::Precursor, 100.0, vec![1, 2], vec![0.1, 0.2], vec![1000, 2000], vec![100.5, 200.5], vec![50.0, 60.0]);
223    /// let ims_spectrum = frame.get_ims_frame();
224    /// ```
225    pub fn get_ims_frame(&self) -> ImsFrame { self.ims_frame.clone() }
226
227    ///
228    /// Convert a given TimsFrame to a vector of TimsSpectrum.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// use mscore::data::spectrum::MsType;
234    /// use mscore::timstof::frame::TimsFrame;
235    ///
236    /// let frame = TimsFrame::new(1, MsType::Precursor, 100.0, vec![1, 2], vec![0.1, 0.2], vec![1000, 2000], vec![100.5, 200.5], vec![50.0, 60.0]);
237    /// let tims_spectra = frame.to_tims_spectra();
238    /// ```
239    pub fn to_tims_spectra(&self) -> Vec<TimsSpectrum> {
240        // use a sorted map where scan is used as key
241        let mut spectra = BTreeMap::<i32, (f64, Vec<i32>, Vec<f64>, Vec<f64>)>::new();
242
243        // all indices and the intensity values are sorted by scan and stored in the map as a tuple (mobility, tof, mz, intensity)
244        for (scan, mobility, tof, mz, intensity) in itertools::multizip((
245            self.scan.iter(),
246            self.ims_frame.mobility.iter(),
247            self.tof.iter(),
248            self.ims_frame.mz.iter(),
249            self.ims_frame.intensity.iter())) {
250            let entry = spectra.entry(*scan).or_insert_with(|| (*mobility, Vec::new(), Vec::new(), Vec::new()));
251            entry.1.push(*tof);
252            entry.2.push(*mz);
253            entry.3.push(*intensity);
254        }
255
256        // convert the map to a vector of TimsSpectrum
257        let mut tims_spectra: Vec<TimsSpectrum> = Vec::new();
258
259        for (scan, (mobility, tof, mz, intensity)) in spectra {
260            let spectrum = IndexedMzSpectrum::new(tof, mz, intensity);
261            tims_spectra.push(TimsSpectrum::new(self.frame_id, scan, self.ims_frame.retention_time, mobility, self.ms_type.clone(), spectrum));
262        }
263
264        tims_spectra
265    }
266
267    ///
268    /// Filter a given TimsFrame by m/z, scan, and intensity.
269    ///
270    /// # Arguments
271    ///
272    /// * `mz_min` - The minimum m/z value.
273    /// * `mz_max` - The maximum m/z value.
274    /// * `scan_min` - The minimum scan value.
275    /// * `scan_max` - The maximum scan value.
276    /// *
277    /// * `intensity_min` - The minimum intensity value.
278    /// * `intensity_max` - The maximum intensity value.
279    ///
280    /// # Examples
281    ///
282    /// ```
283    /// use mscore::data::spectrum::MsType;
284    /// use mscore::timstof::frame::TimsFrame;
285    ///
286    /// let frame = TimsFrame::new(1, MsType::Precursor, 100.0, vec![1, 2], vec![0.1, 0.2], vec![1000, 2000], vec![100.5, 200.5], vec![50.0, 60.0]);
287    /// let filtered_frame = frame.filter_ranged(100.0, 200.0, 1, 2, 0.0, 1.6, 50.0, 60.0, 0, i32::MAX);
288    /// ```
289    pub fn filter_ranged(&self, mz_min: f64, mz_max: f64, scan_min: i32, scan_max: i32, inv_mob_min: f64, inv_mob_max: f64, intensity_min: f64, intensity_max: f64, tof_min: i32, tof_max: i32) -> TimsFrame {
290
291        let mut scan_vec = Vec::new();
292        let mut mobility_vec = Vec::new();
293        let mut tof_vec = Vec::new();
294        let mut mz_vec = Vec::new();
295        let mut intensity_vec = Vec::new();
296
297        for (mz, intensity, scan, mobility, tof) in itertools::multizip((self.ims_frame.mz.iter(), self.ims_frame.intensity.iter(), self.scan.iter(), self.ims_frame.mobility.iter(), self.tof.iter())) {
298            if mz >= &mz_min && mz <= &mz_max && scan >= &scan_min && scan <= &scan_max && mobility >= &inv_mob_min && mobility <= &inv_mob_max && intensity >= &intensity_min && intensity <= &intensity_max && tof >= &tof_min && tof <= &tof_max {
299                scan_vec.push(*scan);
300                mobility_vec.push(*mobility);
301                tof_vec.push(*tof);
302                mz_vec.push(*mz);
303                intensity_vec.push(*intensity);
304            }
305        }
306
307        TimsFrame::new(self.frame_id, self.ms_type.clone(), self.ims_frame.retention_time, scan_vec, mobility_vec, tof_vec, mz_vec, intensity_vec)
308    }
309
310    pub fn top_n(&self, n: usize) -> TimsFrame {
311        let mut indices: Vec<usize> = (0..self.ims_frame.intensity.len()).collect();
312        indices.sort_by(|a, b| self.ims_frame.intensity[*b].partial_cmp(&self.ims_frame.intensity[*a]).unwrap());
313        indices.truncate(n);
314
315        let scan = indices.iter().map(|&i| self.scan[i]).collect();
316        let mobility = indices.iter().map(|&i| self.ims_frame.mobility[i]).collect();
317        let tof = indices.iter().map(|&i| self.tof[i]).collect();
318        let mz = indices.iter().map(|&i| self.ims_frame.mz[i]).collect();
319        let intensity = indices.iter().map(|&i| self.ims_frame.intensity[i]).collect();
320
321        TimsFrame::new(self.frame_id, self.ms_type.clone(), self.ims_frame.retention_time, scan, mobility, tof, mz, intensity)
322    }
323
324    pub fn to_windows_indexed(&self, window_length: f64, overlapping: bool, min_peaks: usize, min_intensity: f64) -> (Vec<i32>, Vec<i32>, Vec<TimsSpectrum>) {
325        // split by scan (ion mobility)
326        let spectra = self.to_tims_spectra();
327
328        let windows: Vec<_> = spectra.iter().map(|spectrum|
329            spectrum.to_windows(window_length, overlapping, min_peaks, min_intensity))
330            .collect();
331
332        let mut scan_indices = Vec::new();
333
334        for tree in windows.iter() {
335            for (_, window) in tree {
336                scan_indices.push(window.scan)
337            }
338        }
339
340        let mut spectra = Vec::new();
341        let mut window_indices = Vec::new();
342
343        for window in windows {
344            for (i, spectrum) in window {
345                spectra.push(spectrum);
346                window_indices.push(i);
347            }
348        }
349
350        (scan_indices, window_indices, spectra)
351    }
352
353    pub fn to_windows(&self, window_length: f64, overlapping: bool, min_peaks: usize, min_intensity: f64) -> Vec<TimsSpectrum> {
354        let (_, _, widows) = self.to_windows_indexed(window_length, overlapping, min_peaks, min_intensity);
355        widows
356    }
357
358    pub fn from_windows(windows: Vec<TimsSpectrum>) -> TimsFrame {
359
360        let first_window = windows.first().unwrap();
361
362        let mut scan = Vec::new();
363        let mut tof = Vec::new();
364        let mut mzs = Vec::new();
365        let mut intensity = Vec::new();
366        let mut mobility = Vec::new();
367
368        for window in windows.iter() {
369            for (i, mz) in window.spectrum.mz_spectrum.mz.iter().enumerate() {
370                scan.push(window.scan);
371                mobility.push(window.mobility);
372                tof.push(window.spectrum.index[i]);
373                mzs.push(*mz);
374                intensity.push(window.spectrum.mz_spectrum.intensity[i]);
375            }
376        }
377
378        TimsFrame::new(first_window.frame_id, first_window.ms_type.clone(), first_window.retention_time, scan, mobility, tof, mzs, intensity)
379    }
380
381    pub fn from_tims_spectra(spectra: Vec<TimsSpectrum>) -> TimsFrame {
382
383        // Helper to quantize mz to an integer key
384        let quantize = |mz: f64| -> i64 {
385            (mz * 1_000_000.0).round() as i64
386        };
387
388        // Step 1: Get frame coordinates
389        let first_spec = spectra.first();
390        let frame_id = match first_spec {
391            Some(first_spec) => first_spec.frame_id,
392            _ => 1
393        };
394
395        let ms_type = match first_spec {
396            Some(first_spec) => first_spec.ms_type.clone(),
397            _ => MsType::Unknown,
398        };
399
400        let retention_time = match first_spec {
401            Some(first_spec) => first_spec.retention_time,
402            _ => 0.0
403        };
404
405        let mut frame_map: BTreeMap<i32, (f64, BTreeMap<i64, (i32, f64)>)> = BTreeMap::new();
406        let mut capacity_count = 0;
407
408        // Step 2: Group by scan and unroll all spectra to a single vector per scan
409        for spectrum in &spectra {
410            let inv_mobility = spectrum.mobility;
411            let entry = frame_map.entry(spectrum.scan).or_insert_with(|| (inv_mobility, BTreeMap::new()));
412            for (i, mz) in spectrum.spectrum.mz_spectrum.mz.iter().enumerate() {
413                let tof = spectrum.spectrum.index[i];
414                let intensity = spectrum.spectrum.mz_spectrum.intensity[i];
415                entry.1.entry(quantize(*mz)).and_modify(|e| *e = (tof, e.1 + intensity)).or_insert((tof, intensity));
416                capacity_count += 1;
417            }
418        }
419
420        // Step 3: Unroll the map to vectors
421        let mut scan = Vec::with_capacity(capacity_count);
422        let mut mobility = Vec::with_capacity(capacity_count);
423        let mut tof = Vec::with_capacity(capacity_count);
424        let mut mzs = Vec::with_capacity(capacity_count);
425        let mut intensity = Vec::with_capacity(capacity_count);
426
427        for (scan_val, (mobility_val, mz_map)) in frame_map {
428            for (mz_val, (tof_val, intensity_val)) in mz_map {
429                scan.push(scan_val);
430                mobility.push(mobility_val);
431                tof.push(tof_val);
432                mzs.push(mz_val as f64 / 1_000_000.0);
433                intensity.push(intensity_val);
434            }
435        }
436
437        TimsFrame::new(frame_id, ms_type, retention_time, scan, mobility, tof, mzs, intensity)
438    }
439
440    /// Create a TimsFrame from a vector of TimsSpectrum, filtering during construction.
441    /// This is more efficient than calling from_tims_spectra followed by filter_ranged,
442    /// as it avoids creating an intermediate unfiltered frame.
443    pub fn from_tims_spectra_filtered(
444        spectra: Vec<TimsSpectrum>,
445        mz_min: f64,
446        mz_max: f64,
447        scan_min: i32,
448        scan_max: i32,
449        inv_mob_min: f64,
450        inv_mob_max: f64,
451        intensity_min: f64,
452        intensity_max: f64,
453    ) -> TimsFrame {
454        let quantize = |mz: f64| -> i64 {
455            (mz * 1_000_000.0).round() as i64
456        };
457
458        let first_spec = spectra.first();
459        let frame_id = first_spec.map(|s| s.frame_id).unwrap_or(1);
460        let ms_type = first_spec.map(|s| s.ms_type.clone()).unwrap_or(MsType::Unknown);
461        let retention_time = first_spec.map(|s| s.retention_time).unwrap_or(0.0);
462
463        let mut frame_map: BTreeMap<i32, (f64, BTreeMap<i64, (i32, f64)>)> = BTreeMap::new();
464        let mut capacity_count = 0;
465
466        // Group by scan, filtering out-of-range scans and mobilities
467        for spectrum in &spectra {
468            // Early filter on scan range
469            if spectrum.scan < scan_min || spectrum.scan > scan_max {
470                continue;
471            }
472            // Early filter on mobility range
473            if spectrum.mobility < inv_mob_min || spectrum.mobility > inv_mob_max {
474                continue;
475            }
476
477            let inv_mobility = spectrum.mobility;
478            let entry = frame_map.entry(spectrum.scan).or_insert_with(|| (inv_mobility, BTreeMap::new()));
479
480            for (i, mz) in spectrum.spectrum.mz_spectrum.mz.iter().enumerate() {
481                // Filter on mz range
482                if *mz < mz_min || *mz > mz_max {
483                    continue;
484                }
485                let intensity = spectrum.spectrum.mz_spectrum.intensity[i];
486                // Filter on intensity range
487                if intensity < intensity_min || intensity > intensity_max {
488                    continue;
489                }
490
491                let tof = spectrum.spectrum.index[i];
492                entry.1.entry(quantize(*mz)).and_modify(|e| *e = (tof, e.1 + intensity)).or_insert((tof, intensity));
493                capacity_count += 1;
494            }
495        }
496
497        // Unroll the map to vectors
498        let mut scan = Vec::with_capacity(capacity_count);
499        let mut mobility = Vec::with_capacity(capacity_count);
500        let mut tof = Vec::with_capacity(capacity_count);
501        let mut mzs = Vec::with_capacity(capacity_count);
502        let mut intensity = Vec::with_capacity(capacity_count);
503
504        for (scan_val, (mobility_val, mz_map)) in frame_map {
505            for (mz_val, (tof_val, intensity_val)) in mz_map {
506                // Final intensity filter after aggregation (sum may push over threshold)
507                if intensity_val < intensity_min || intensity_val > intensity_max {
508                    continue;
509                }
510                scan.push(scan_val);
511                mobility.push(mobility_val);
512                tof.push(tof_val);
513                mzs.push(mz_val as f64 / 1_000_000.0);
514                intensity.push(intensity_val);
515            }
516        }
517
518        TimsFrame::new(frame_id, ms_type, retention_time, scan, mobility, tof, mzs, intensity)
519    }
520
521    pub fn to_dense_windows(&self, window_length: f64, overlapping: bool, min_peaks: usize, min_intensity: f64, resolution: i32) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<i32>, Vec<i32>, usize, usize) {
522        let factor = (10.0f64).powi(resolution);
523        let num_colums = ((window_length * factor).round() + 1.0) as usize;
524
525        let (scans, window_indices, spectra) = self.to_windows_indexed(window_length, overlapping, min_peaks, min_intensity);
526        let mut mobilities = Vec::with_capacity(spectra.len());
527        let mut mzs = Vec::with_capacity(spectra.len());
528
529        // go over all spectra and fill mobilities and mzs
530        for spectrum in &spectra {
531            mobilities.push(spectrum.mobility);
532            mzs.push(spectrum.spectrum.mz_spectrum.mz.first().unwrap().clone());
533        }
534
535        let vectorized_spectra = spectra.iter().map(|spectrum| spectrum.vectorized(resolution)).collect::<Vec<_>>();
536
537        let mut flat_matrix: Vec<f64> = vec![0.0; spectra.len() * num_colums];
538
539        for (row_index, (window_index, spectrum)) in itertools::multizip((&window_indices, vectorized_spectra)).enumerate() {
540
541            let vectorized_window_index = match *window_index >= 0 {
542                true => (*window_index as f64 * window_length * factor).round() as i32,
543                false => (((-1.0 * (*window_index as f64)) * window_length - (0.5 * window_length)) * factor).round() as i32,
544            };
545
546            for (i, index) in spectrum.vector.mz_vector.indices.iter().enumerate() {
547                let zero_based_index = (index - vectorized_window_index) as usize;
548                let current_index = row_index * num_colums + zero_based_index;
549                flat_matrix[current_index] = spectrum.vector.mz_vector.values[i];
550            }
551
552        }
553        (flat_matrix, mobilities, mzs, scans, window_indices, spectra.len(), num_colums)
554    }
555
556
557    pub fn to_indexed_mz_spectrum(&self) -> IndexedMzSpectrum {
558        let mut grouped_data: BTreeMap<i32, Vec<(f64, f64)>> = BTreeMap::new();
559
560        // Group by 'tof' with 'mz' and 'intensity'
561        for (&tof, (&mz, &intensity)) in self.tof.iter().zip(self.ims_frame.mz.iter().zip(self.ims_frame.intensity.iter())) {
562            grouped_data.entry(tof).or_insert_with(Vec::new).push((mz, intensity));
563        }
564
565        let mut index = Vec::new();
566        let mut mz = Vec::new();
567        let mut intensity = Vec::new();
568
569        for (&tof_val, values) in &grouped_data {
570            let sum_intensity: f64 = values.iter().map(|&(_, i)| i).sum();
571            let avg_mz: f64 = values.iter().map(|&(m, _)| m).sum::<f64>() / values.len() as f64;
572
573            index.push(tof_val);
574            mz.push(avg_mz);
575            intensity.push(sum_intensity);
576        }
577
578        IndexedMzSpectrum {
579            index,
580            mz_spectrum: MzSpectrum::new(mz, intensity),
581        }
582    }
583
584    pub fn generate_random_sample(&self, take_probability: f64) -> TimsFrame {
585        let mut rng = rand::thread_rng();
586        self.generate_random_sample_with_rng(take_probability, &mut rng)
587    }
588
589    /// Same as `generate_random_sample`, but drawing from a caller-supplied RNG so that the
590    /// selection is reproducible (used by the seeded, parallel reference-noise overlay).
591    pub fn generate_random_sample_with_rng<R: Rng>(&self, take_probability: f64, rng: &mut R) -> TimsFrame {
592        assert!(take_probability >= 0.0 && take_probability <= 1.0);
593
594        let mut scan = Vec::new();
595        let mut mobility = Vec::new();
596        let mut tof = Vec::new();
597        let mut mz = Vec::new();
598        let mut intensity = Vec::new();
599
600        for (s, m, t, mz_val, i) in itertools::multizip((self.scan.iter(), self.ims_frame.mobility.iter(), self.tof.iter(), self.ims_frame.mz.iter(), self.ims_frame.intensity.iter())) {
601            if rng.gen::<f64>() <= take_probability {
602                scan.push(*s);
603                mobility.push(*m);
604                tof.push(*t);
605                mz.push(*mz_val);
606                intensity.push(*i);
607            }
608        }
609
610        TimsFrame::new(self.frame_id, self.ms_type.clone(), self.ims_frame.retention_time, scan, mobility, tof, mz, intensity)
611    }
612
613    pub fn to_noise_annotated_tims_frame(&self) -> TimsFrameAnnotated {
614        let mut annotations = Vec::with_capacity(self.ims_frame.mz.len());
615        let tof_values = self.tof.clone();
616        let mz_values = self.ims_frame.mz.clone();
617        let scan_values = self.scan.clone();
618        let inv_mobility_values = self.ims_frame.mobility.clone();
619        let intensity_values = self.ims_frame.intensity.clone();
620
621        for intensity in intensity_values.iter() {
622            annotations.push(PeakAnnotation::new_random_noise(*intensity));
623        }
624
625        TimsFrameAnnotated::new(
626            self.frame_id,
627            self.ims_frame.retention_time,
628            self.ms_type.clone(),
629            tof_values.iter().map(|&x| x as u32).collect(),
630            (*mz_values).clone(),
631            scan_values.iter().map(|&x| x as u32).collect(),
632            (*inv_mobility_values).clone(),
633            (*intensity_values).clone(),
634            annotations,
635        )
636    }
637
638    pub fn get_inverse_mobility_along_scan_marginal(&self) -> f64 {
639        let mut marginal_map: BTreeMap<i32, (f64, f64)> = BTreeMap::new();
640        // go over all data points of scan, inv_mob and intensity
641        for (scan, inv_mob, intensity) in izip!(self.scan.iter(), self.ims_frame.mobility.iter(), self.ims_frame.intensity.iter()) {
642            // create a key for the map
643            let key = *scan;
644            // get the entry from the map or insert a new one
645            let entry = marginal_map.entry(key).or_insert((0.0, 0.0));
646            // update the entry with the current intensity adding it to the existing intensity
647            entry.0 += *intensity;
648            // update the entry with the current inverse mobility, overwriting the existing value
649            entry.1 = *inv_mob;
650        }
651
652        // get the inverse mobility with the highest intensity
653        let (_, max_inv_mob) = marginal_map.iter().max_by(|a, b| a.1.0.partial_cmp(&b.1.0).unwrap_or(std::cmp::Ordering::Equal)).unwrap_or((&0, &(0.0, 0.0))).1;
654
655        *max_inv_mob
656    }
657
658    /// Calculate the weighted mean and variance of `inv_mob` values based on their intensities.
659    pub fn get_mobility_mean_and_variance(&self) -> (f64, f64) {
660        let mut mobility_map: BTreeMap<OrderedFloat<f64>, f64> = BTreeMap::new();
661
662        // Aggregate intensity values for each `inv_mob`
663        for (inv_mob, intensity) in izip!(self.ims_frame.mobility.iter(), self.ims_frame.intensity.iter()) {
664            let entry = mobility_map.entry(OrderedFloat(*inv_mob)).or_insert(0.0);
665            *entry += *intensity;
666        }
667
668        // Calculate weighted mean
669        let mut total_weight = 0.0;
670        let mut weighted_sum = 0.0;
671        for (&inv_mob, &intensity) in &mobility_map {
672            total_weight += intensity;
673            weighted_sum += inv_mob.into_inner() * intensity;
674        }
675        let mean = weighted_sum / total_weight;
676
677        // Calculate weighted variance
678        let mut weighted_squared_diff_sum = 0.0;
679        for (&inv_mob, &intensity) in &mobility_map {
680            let diff = inv_mob.into_inner() - mean;
681            weighted_squared_diff_sum += intensity * diff * diff;
682        }
683        let variance = weighted_squared_diff_sum / total_weight;
684
685        (mean, variance)
686    }
687
688    pub fn get_tims_spectrum(&self, scan_number: i32) -> Option<TimsSpectrum> {
689        let mut tof = Vec::new();
690        let mut mz = Vec::new();
691        let mut intensity = Vec::new();
692
693        for (s, t, m, i) in itertools::multizip((self.scan.iter(), self.tof.iter(), self.ims_frame.mz.iter(), self.ims_frame.intensity.iter())) {
694            if *s == scan_number {
695                tof.push(*t);
696                mz.push(*m);
697                intensity.push(*i);
698            }
699        }
700
701        if mz.is_empty() {
702            return None;
703        }
704
705        let mobility = self.ims_frame.mobility.iter()
706            .zip(&self.scan)
707            .find(|(_, s)| **s == scan_number)
708            .map(|(m, _)| *m)?;
709
710        Some(TimsSpectrum {
711            frame_id: self.frame_id,
712            scan: scan_number,
713            retention_time: self.ims_frame.retention_time,
714            mobility,
715            ms_type: self.ms_type.clone(),
716            spectrum: IndexedMzSpectrum::new(tof, mz, intensity),
717        })
718    }
719
720    pub fn fold_along_scan_axis(self, fold_width: usize) -> TimsFrame {
721        // extract tims spectra from frame
722        let spectra = self.to_tims_spectra();
723
724        // create a new collection of merged spectra,where spectra are first grouped by the key they create when divided by fold_width
725        // and then merge them by addition
726        let mut merged_spectra: BTreeMap<i32, TimsSpectrum> = BTreeMap::new();
727        for spectrum in spectra {
728            let key = spectrum.scan / fold_width as i32;
729
730            // if the key already exists, merge the spectra
731            if let Some(existing_spectrum) = merged_spectra.get_mut(&key) {
732
733                let merged_spectrum = existing_spectrum.clone() + spectrum;
734                // update the existing spectrum with the merged one
735                *existing_spectrum = merged_spectrum;
736
737            } else {
738                // otherwise, insert the new spectrum
739                merged_spectra.insert(key, spectrum);
740            }
741        }
742
743        // convert the merged spectra back to a TimsFrame
744        TimsFrame::from_tims_spectra(merged_spectra.into_values().collect())
745    }
746}
747
748struct AggregateData {
749    intensity_sum: f64,
750    ion_mobility_sum: f64,
751    tof_sum: i64,
752    count: i32,
753}
754
755impl std::ops::Add for TimsFrame {
756    type Output = Self;
757    fn add(self, other: Self) -> TimsFrame {
758        let mut combined_map: BTreeMap<(i32, i64), AggregateData> = BTreeMap::new();
759
760        let quantize = |mz: f64| -> i64 {
761            (mz * 1_000_000.0).round() as i64
762        };
763
764        let add_to_map = |map: &mut BTreeMap<(i32, i64), AggregateData>, mz, ion_mobility, tof, scan, intensity| {
765            let key = (scan, quantize(mz));
766            let entry = map.entry(key).or_insert(AggregateData { intensity_sum: 0.0, ion_mobility_sum: 0.0, tof_sum: 0, count: 0 });
767            entry.intensity_sum += intensity;
768            entry.ion_mobility_sum += ion_mobility;
769            entry.tof_sum += tof as i64;
770            entry.count += 1;
771        };
772
773        for (mz, tof, ion_mobility, scan, intensity) in izip!(self.ims_frame.mz.iter(), self.tof.iter(), self.ims_frame.mobility.iter(), self.scan.iter(), self.ims_frame.intensity.iter()) {
774            add_to_map(&mut combined_map, *mz, *ion_mobility, *tof, *scan, *intensity);
775        }
776
777        for (mz, tof, ion_mobility, scan, intensity) in izip!(other.ims_frame.mz.iter(), other.tof.iter(), other.ims_frame.mobility.iter(), other.scan.iter(), other.ims_frame.intensity.iter()) {
778            add_to_map(&mut combined_map, *mz, *ion_mobility, *tof, *scan, *intensity);
779        }
780
781        let mut mz_combined = Vec::new();
782        let mut tof_combined = Vec::new();
783        let mut ion_mobility_combined = Vec::new();
784        let mut scan_combined = Vec::new();
785        let mut intensity_combined = Vec::new();
786
787        for ((scan, quantized_mz), data) in combined_map {
788            mz_combined.push(quantized_mz as f64 / 1_000_000.0);
789            tof_combined.push(data.tof_sum / data.count as i64);
790            ion_mobility_combined.push(data.ion_mobility_sum / data.count as f64);
791            scan_combined.push(scan);
792            intensity_combined.push(data.intensity_sum);
793        }
794
795        let frame = TimsFrame {
796            frame_id: self.frame_id,
797            ms_type: if self.ms_type == other.ms_type { self.ms_type.clone() } else { MsType::Unknown },
798            scan: scan_combined,
799            tof: tof_combined.iter().map(|&x| x as i32).collect(),
800            ims_frame: ImsFrame::new(
801                self.ims_frame.retention_time,
802                ion_mobility_combined,
803                mz_combined,
804                intensity_combined,
805            ),
806        };
807
808        return frame;
809    }
810}
811
812impl fmt::Display for TimsFrame {
813    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
814
815        let (mz, i) = self.ims_frame.mz.iter()
816            .zip(self.ims_frame.intensity.iter())
817            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
818            .unwrap();
819
820        write!(f, "TimsFrame(id: {}, type: {}, rt: {}, data points: {}, max by intensity: (mz: {}, intensity: {}))",
821               self.frame_id, self.ms_type, self.ims_frame.retention_time, self.scan.len(), format!("{:.3}", mz), i)
822    }
823}
824
825impl Vectorized<TimsFrameVectorized> for TimsFrame {
826    fn vectorized(&self, resolution: i32) -> TimsFrameVectorized {
827        let binned_frame = self.to_resolution(resolution);
828        // Translate the m/z values into integer indices
829        let indices: Vec<i32> = binned_frame.ims_frame.mz.iter().map(|&mz| (mz * 10f64.powi(resolution)).round() as i32).collect();
830        // Create a vector of values
831        return TimsFrameVectorized {
832            frame_id: self.frame_id,
833            ms_type: self.ms_type.clone(),
834            scan: binned_frame.scan,
835            tof: binned_frame.tof,
836            ims_frame: ImsFrameVectorized {
837                retention_time: binned_frame.ims_frame.retention_time,
838                mobility: (*binned_frame.ims_frame.mobility).clone(),
839                indices,
840                values: (*binned_frame.ims_frame.intensity).clone(),
841                resolution,
842            },
843        };
844    }
845}
846
847///
848/// Convert a given TimsFrame to a vector of TimsSpectrum.
849///
850/// # Arguments
851///
852/// * `resolution` - The resolution to which the m/z values should be rounded.
853///
854/// # Examples
855///
856/// ```
857/// use mscore::data::spectrum::MsType;
858/// use mscore::timstof::frame::TimsFrame;
859/// use mscore::timstof::spectrum::TimsSpectrum;
860/// use mscore::data::spectrum::IndexedMzSpectrum;
861/// use mscore::data::spectrum::ToResolution;
862///
863/// let frame = TimsFrame::new(1, MsType::Precursor, 100.0, vec![1, 2], vec![0.1, 0.2], vec![1000, 2000], vec![100.5, 200.5], vec![50.0, 60.0]);
864/// let low_res_frame = frame.to_resolution(1);
865/// ```
866impl ToResolution for TimsFrame {
867    fn to_resolution(&self, resolution: i32) -> TimsFrame {
868        let factor = (10.0f64).powi(resolution);
869
870        // Using a tuple of (scan, mz_bin) as a key
871        // Value will store sum of intensities, sum of tofs, sum of mobilities and their count for averaging
872        let mut bin_map: BTreeMap<(i32, i32), (f64, f64, f64, i32)> = BTreeMap::new();
873
874        for i in 0..self.ims_frame.mz.len() {
875            let rounded_mz = (self.ims_frame.mz[i] * factor).round() as i32;
876            let scan_val = self.scan[i];
877            let intensity_val = self.ims_frame.intensity[i] as f64;
878            let tof_val = self.tof[i] as f64;
879            let mobility_val = self.ims_frame.mobility[i] as f64;
880
881            let entry = bin_map.entry((scan_val, rounded_mz)).or_insert((0.0, 0.0, 0.0, 0));
882            entry.0 += intensity_val;
883            entry.1 += tof_val;
884            entry.2 += mobility_val;
885            entry.3 += 1;
886        }
887
888        let mut new_mz = Vec::with_capacity(bin_map.len());
889        let mut new_scan = Vec::with_capacity(bin_map.len());
890        let mut new_intensity = Vec::with_capacity(bin_map.len());
891        let mut new_tof = Vec::with_capacity(bin_map.len());
892        let mut new_mobility = Vec::with_capacity(bin_map.len());
893
894        for ((scan, mz_bin), (intensity_sum, tof_sum, mobility_sum, count)) in bin_map {
895            new_mz.push(mz_bin as f64 / factor);
896            new_scan.push(scan);
897            new_intensity.push(intensity_sum);
898            new_tof.push((tof_sum / count as f64) as i32);
899            new_mobility.push(mobility_sum / count as f64);
900        }
901
902        TimsFrame {
903            frame_id: self.frame_id,
904            ms_type: self.ms_type.clone(),
905            scan: new_scan,
906            tof: new_tof,
907            ims_frame: ImsFrame::new(
908                self.ims_frame.retention_time,
909                new_mobility,
910                new_mz,
911                new_intensity,
912            ),
913        }
914    }
915}
916
917#[derive(Clone)]
918pub struct TimsFrameVectorized {
919    pub frame_id: i32,
920    pub ms_type: MsType,
921    pub scan: Vec<i32>,
922    pub tof: Vec<i32>,
923    pub ims_frame: ImsFrameVectorized,
924}
925
926impl TimsFrameVectorized {
927    pub fn filter_ranged(&self, mz_min: f64, mz_max: f64, scan_min: i32, scan_max: i32, inv_mob_min: f64, inv_mob_max: f64, intensity_min: f64, intensity_max: f64) -> TimsFrameVectorized {
928        let mut scan_vec = Vec::new();
929        let mut mobility_vec = Vec::new();
930        let mut tof_vec = Vec::new();
931        let mut mz_vec = Vec::new();
932        let mut intensity_vec = Vec::new();
933        let mut indices_vec = Vec::new();
934
935        for (mz, intensity, scan, mobility, tof, index) in itertools::multizip((&self.ims_frame.values, &self.ims_frame.values, &self.scan, &self.ims_frame.mobility, &self.tof, &self.ims_frame.indices)) {
936            if mz >= &mz_min && mz <= &mz_max && scan >= &scan_min && scan <= &scan_max && mobility >= &inv_mob_min && mobility <= &inv_mob_max && intensity >= &intensity_min && intensity <= &intensity_max {
937                scan_vec.push(*scan);
938                mobility_vec.push(*mobility);
939                tof_vec.push(*tof);
940                mz_vec.push(*mz);
941                intensity_vec.push(*intensity);
942                indices_vec.push(*index);
943            }
944        }
945
946        TimsFrameVectorized {
947            frame_id: self.frame_id,
948            ms_type: self.ms_type.clone(),
949            scan: scan_vec,
950            tof: tof_vec,
951            ims_frame: ImsFrameVectorized {
952                retention_time: self.ims_frame.retention_time,
953                mobility: mobility_vec,
954                indices: indices_vec,
955                values: mz_vec,
956                resolution: self.ims_frame.resolution,
957            },
958        }
959    }
960}
961
962impl fmt::Display for TimsFrameVectorized {
963    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
964
965        let (mz, i) = self.ims_frame.values.iter()
966            .zip(&self.ims_frame.values)
967            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
968            .unwrap();
969
970        write!(f, "TimsFrame(id: {}, type: {}, rt: {}, data points: {}, max by intensity: (mz: {}, intensity: {}))",
971               self.frame_id, self.ms_type, self.ims_frame.retention_time, self.scan.len(), format!("{:.3}", mz), i)
972    }
973}