rustdf/sim/
dda.rs

1use mscore::data::peptide::{PeptideIon, PeptideProductIonSeriesCollection};
2use mscore::data::spectrum::{IndexedMzSpectrum, MsType, MzSpectrum};
3use mscore::simulation::annotation::{
4    MzSpectrumAnnotated, TimsFrameAnnotated, TimsSpectrumAnnotated,
5};
6use mscore::timstof::frame::TimsFrame;
7use mscore::timstof::quadrupole::{IonTransmission, TimsTransmissionDDA};
8use mscore::timstof::spectrum::TimsSpectrum;
9use std::collections::{BTreeMap, HashSet};
10use std::path::Path;
11
12use rayon::prelude::*;
13use rayon::ThreadPoolBuilder;
14use crate::sim::handle::TimsTofSyntheticsDataHandle;
15use crate::sim::precursor::TimsTofSyntheticsPrecursorFrameBuilder;
16
17pub struct TimsTofSyntheticsFrameBuilderDDA {
18    pub path: String,
19    pub precursor_frame_builder: TimsTofSyntheticsPrecursorFrameBuilder,
20    pub transmission_settings: TimsTransmissionDDA,
21    pub fragment_ions:
22        Option<BTreeMap<(u32, i8, i32), (PeptideProductIonSeriesCollection, Vec<MzSpectrum>)>>,
23    pub fragment_ions_annotated: Option<
24        BTreeMap<(u32, i8, i32), (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>)>,
25    >,
26}
27
28impl TimsTofSyntheticsFrameBuilderDDA {
29    pub fn new(path: &Path, with_annotations: bool, num_threads: usize) -> Self {
30
31        let handle = TimsTofSyntheticsDataHandle::new(path).unwrap();
32        let fragment_ions = handle.read_fragment_ions().unwrap();
33        let transmission_settings = handle.get_transmission_dda();
34
35        let synthetics = TimsTofSyntheticsPrecursorFrameBuilder::new(path).unwrap();
36
37        match with_annotations {
38            true => {
39                let fragment_ions =
40                    Some(TimsTofSyntheticsDataHandle::build_fragment_ions_annotated(
41                        &synthetics.peptides,
42                        &fragment_ions,
43                        num_threads,
44                    ));
45                Self {
46                    path: path.to_str().unwrap().to_string(),
47                    precursor_frame_builder: synthetics,
48                    transmission_settings,
49                    fragment_ions: None,
50                    fragment_ions_annotated: fragment_ions,
51                }
52            }
53            false => {
54                let fragment_ions = Some(TimsTofSyntheticsDataHandle::build_fragment_ions(
55                    &synthetics.peptides,
56                    &fragment_ions,
57                    num_threads,
58                ));
59                Self {
60                    path: path.to_str().unwrap().to_string(),
61                    precursor_frame_builder: synthetics,
62                    transmission_settings,
63                    fragment_ions,
64                    fragment_ions_annotated: None,
65                }
66            }
67        }
68    }
69    /// Build a frame for DDA synthetic experiment
70    ///
71    /// # Arguments
72    ///
73    /// * `frame_id` - The frame id
74    /// * `fragmentation` - A boolean indicating if fragmentation is enabled, if false, the frame has same mz distribution as the precursor frame but will be quadrupole filtered
75    ///
76    /// # Returns
77    ///
78    /// A TimsFrame
79    ///
80    pub fn build_frame(
81        &self,
82        frame_id: u32,
83        fragmentation: bool,
84        mz_noise_precursor: bool,
85        uniform: bool,
86        precursor_noise_ppm: f64,
87        mz_noise_fragment: bool,
88        fragment_noise_ppm: f64,
89        right_drag: bool,
90    ) -> TimsFrame {
91        // determine if the frame is a precursor frame
92        match self
93            .precursor_frame_builder
94            .precursor_frame_id_set
95            .contains(&frame_id)
96        {
97            true => self.build_ms1_frame(
98                frame_id,
99                mz_noise_precursor,
100                uniform,
101                precursor_noise_ppm,
102                right_drag,
103            ),
104            false => self.build_ms2_frame(
105                frame_id,
106                fragmentation,
107                mz_noise_fragment,
108                uniform,
109                fragment_noise_ppm,
110                right_drag,
111            ),
112        }
113    }
114
115    pub fn build_frame_annotated(
116        &self,
117        frame_id: u32,
118        fragmentation: bool,
119        mz_noise_precursor: bool,
120        uniform: bool,
121        precursor_noise_ppm: f64,
122        mz_noise_fragment: bool,
123        fragment_noise_ppm: f64,
124        right_drag: bool,
125    ) -> TimsFrameAnnotated {
126        match self
127            .precursor_frame_builder
128            .precursor_frame_id_set
129            .contains(&frame_id)
130        {
131            true => self.build_ms1_frame_annotated(
132                frame_id,
133                mz_noise_precursor,
134                uniform,
135                precursor_noise_ppm,
136                right_drag,
137            ),
138            false => self.build_ms2_frame_annotated(
139                frame_id,
140                fragmentation,
141                mz_noise_fragment,
142                uniform,
143                fragment_noise_ppm,
144                right_drag,
145            ),
146        }
147    }
148
149    pub fn get_fragment_ion_ids(&self, precursor_frame_ids: Vec<u32>) -> Vec<u32> {
150        let mut peptide_ids: HashSet<u32> = HashSet::new();
151        // get all peptide ids for the precursor frame ids
152        for frame_id in precursor_frame_ids {
153            for (peptide_id, peptide) in self.precursor_frame_builder.peptides.iter() {
154                if peptide.frame_start <= frame_id && peptide.frame_end >= frame_id {
155                    peptide_ids.insert(*peptide_id);
156                }
157            }
158        }
159        // get all ion ids for the peptide ids
160        let mut result: Vec<u32> = Vec::new();
161        for item in peptide_ids {
162            let ions = self.precursor_frame_builder.ions.get(&item).unwrap();
163            for ion in ions.iter() {
164                result.push(ion.ion_id);
165            }
166        }
167        result
168    }
169
170    pub fn build_frames(
171        &self,
172        frame_ids: Vec<u32>,
173        fragmentation: bool,
174        mz_noise_precursor: bool,
175        uniform: bool,
176        precursor_noise_ppm: f64,
177        mz_noise_fragment: bool,
178        fragment_noise_ppm: f64,
179        right_drag: bool,
180        num_threads: usize,
181    ) -> Vec<TimsFrame> {
182        let thread_pool = ThreadPoolBuilder::new()
183            .num_threads(num_threads)
184            .build()
185            .unwrap();
186        let mut tims_frames: Vec<TimsFrame> = Vec::new();
187
188        thread_pool.install(|| {
189            tims_frames = frame_ids
190                .par_iter()
191                .map(|frame_id| {
192                    self.build_frame(
193                        *frame_id,
194                        fragmentation,
195                        mz_noise_precursor,
196                        uniform,
197                        precursor_noise_ppm,
198                        mz_noise_fragment,
199                        fragment_noise_ppm,
200                        right_drag,
201                    )
202                })
203                .collect();
204        });
205
206        tims_frames.sort_by(|a, b| a.frame_id.cmp(&b.frame_id));
207
208        tims_frames
209    }
210    pub fn build_frames_annotated(
211        &self,
212        frame_ids: Vec<u32>,
213        fragmentation: bool,
214        mz_noise_precursor: bool,
215        uniform: bool,
216        precursor_noise_ppm: f64,
217        mz_noise_fragment: bool,
218        fragment_noise_ppm: f64,
219        right_drag: bool,
220        num_threads: usize,
221    ) -> Vec<TimsFrameAnnotated> {
222        let thread_pool = ThreadPoolBuilder::new()
223            .num_threads(num_threads)
224            .build()
225            .unwrap();
226        let mut tims_frames: Vec<TimsFrameAnnotated> = Vec::new();
227
228        thread_pool.install(|| {
229            tims_frames = frame_ids
230                .par_iter()
231                .map(|frame_id| {
232                    self.build_frame_annotated(
233                        *frame_id,
234                        fragmentation,
235                        mz_noise_precursor,
236                        uniform,
237                        precursor_noise_ppm,
238                        mz_noise_fragment,
239                        fragment_noise_ppm,
240                        right_drag,
241                    )
242                })
243                .collect();
244        });
245
246        tims_frames.sort_by(|a, b| a.frame_id.cmp(&b.frame_id));
247
248        tims_frames
249    }
250
251    fn build_ms1_frame(
252        &self,
253        frame_id: u32,
254        mz_noise_precursor: bool,
255        uniform: bool,
256        precursor_ppm: f64,
257        right_drag: bool,
258    ) -> TimsFrame {
259        let mut tims_frame = self.precursor_frame_builder.build_precursor_frame(
260            frame_id,
261            mz_noise_precursor,
262            uniform,
263            precursor_ppm,
264            right_drag,
265        );
266        let intensities_rounded = tims_frame
267            .ims_frame
268            .intensity
269            .iter()
270            .map(|x| x.round())
271            .collect::<Vec<_>>();
272        tims_frame.ims_frame.intensity = intensities_rounded;
273        tims_frame
274    }
275
276    fn build_ms1_frame_annotated(
277        &self,
278        frame_id: u32,
279        mz_noise_precursor: bool,
280        uniform: bool,
281        precursor_ppm: f64,
282        right_drag: bool,
283    ) -> TimsFrameAnnotated {
284        let mut tims_frame = self
285            .precursor_frame_builder
286            .build_precursor_frame_annotated(
287                frame_id,
288                mz_noise_precursor,
289                uniform,
290                precursor_ppm,
291                right_drag,
292            );
293        let intensities_rounded = tims_frame
294            .intensity
295            .iter()
296            .map(|x| x.round())
297            .collect::<Vec<_>>();
298        tims_frame.intensity = intensities_rounded;
299        tims_frame
300    }
301
302    fn build_ms2_frame(
303        &self,
304        frame_id: u32,
305        fragmentation: bool,
306        mz_noise_fragment: bool,
307        uniform: bool,
308        fragment_ppm: f64,
309        right_drag: bool,
310    ) -> TimsFrame {
311        match fragmentation {
312            false => {
313                let mut frame = self.transmission_settings.transmit_tims_frame(
314                    &self.build_ms1_frame(
315                        frame_id,
316                        mz_noise_fragment,
317                        uniform,
318                        fragment_ppm,
319                        right_drag,
320                    ),
321                    None,
322                );
323                let intensities_rounded = frame
324                    .ims_frame
325                    .intensity
326                    .iter()
327                    .map(|x| x.round())
328                    .collect::<Vec<_>>();
329                frame.ims_frame.intensity = intensities_rounded;
330                frame.ms_type = MsType::FragmentDia;
331                frame
332            }
333            true => {
334                let mut frame = self.build_fragment_frame(
335                    frame_id,
336                    &self.fragment_ions.as_ref().unwrap(),
337                    mz_noise_fragment,
338                    uniform,
339                    fragment_ppm,
340                    None,
341                    None,
342                    None,
343                    Some(right_drag),
344                );
345                let intensities_rounded = frame
346                    .ims_frame
347                    .intensity
348                    .iter()
349                    .map(|x| x.round())
350                    .collect::<Vec<_>>();
351                frame.ims_frame.intensity = intensities_rounded;
352                frame
353            }
354        }
355    }
356
357    fn build_ms2_frame_annotated(
358        &self,
359        frame_id: u32,
360        fragmentation: bool,
361        mz_noise_fragment: bool,
362        uniform: bool,
363        fragment_ppm: f64,
364        right_drag: bool,
365    ) -> TimsFrameAnnotated {
366        match fragmentation {
367            false => {
368                let mut frame = self.transmission_settings.transmit_tims_frame_annotated(
369                    &self.build_ms1_frame_annotated(
370                        frame_id,
371                        mz_noise_fragment,
372                        uniform,
373                        fragment_ppm,
374                        right_drag,
375                    ),
376                    None,
377                );
378                let intensities_rounded = frame
379                    .intensity
380                    .iter()
381                    .map(|x| x.round())
382                    .collect::<Vec<_>>();
383                frame.intensity = intensities_rounded;
384                frame.ms_type = MsType::FragmentDia;
385                frame
386            }
387            true => {
388                let mut frame = self.build_fragment_frame_annotated(
389                    frame_id,
390                    &self.fragment_ions_annotated.as_ref().unwrap(),
391                    mz_noise_fragment,
392                    uniform,
393                    fragment_ppm,
394                    None,
395                    None,
396                    None,
397                    Some(right_drag),
398                );
399                let intensities_rounded = frame
400                    .intensity
401                    .iter()
402                    .map(|x| x.round())
403                    .collect::<Vec<_>>();
404                frame.intensity = intensities_rounded;
405                frame
406            }
407        }
408    }
409
410    /// Build a fragment frame
411    ///
412    /// # Arguments
413    ///
414    /// * `frame_id` - The frame id
415    /// * `mz_min` - The minimum m/z value in fragment spectrum
416    /// * `mz_max` - The maximum m/z value in fragment spectrum
417    /// * `intensity_min` - The minimum intensity value in fragment spectrum
418    ///
419    /// # Returns
420    ///
421    /// A TimsFrame
422    ///
423    fn build_fragment_frame(
424        &self,
425        frame_id: u32,
426        fragment_ions: &BTreeMap<
427            (u32, i8, i32),
428            (PeptideProductIonSeriesCollection, Vec<MzSpectrum>),
429        >,
430        mz_noise_fragment: bool,
431        uniform: bool,
432        fragment_ppm: f64,
433        mz_min: Option<f64>,
434        mz_max: Option<f64>,
435        intensity_min: Option<f64>,
436        right_drag: Option<bool>,
437    ) -> TimsFrame {
438        // check frame id
439        let ms_type = match self
440            .precursor_frame_builder
441            .precursor_frame_id_set
442            .contains(&frame_id)
443        {
444            false => MsType::FragmentDda,
445            true => MsType::Unknown,
446        };
447
448        // pull out RT once so we don’t unwrap twice
449        let rt = *self
450            .precursor_frame_builder
451            .frame_to_rt
452            .get(&frame_id)
453            .expect("frame_to_rt should always have this frame") as f64;
454
455        // if no PASEF meta *or* no abundances, return an empty frame
456        if self
457            .transmission_settings
458            .pasef_meta
459            .get(&(frame_id as i32))
460            .is_none()
461            || !self
462            .precursor_frame_builder
463            .frame_to_abundances
464            .contains_key(&frame_id)
465        {
466            return TimsFrame::new(
467                frame_id as i32,
468                ms_type.clone(),
469                rt,
470                vec![], vec![], vec![], vec![], vec![],
471            );
472        }
473
474        let mut tims_spectra: Vec<TimsSpectrum> = Vec::new();
475
476        // Get the peptide ids and abundances for the frame, should now save to unwrap since we checked if the frame is in the map
477        let (peptide_ids, frame_abundances) = self
478            .precursor_frame_builder
479            .frame_to_abundances
480            .get(&frame_id)
481            .unwrap();
482
483        // Go over all peptides in the frame with their respective abundances
484        for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
485            // jump to next peptide if the peptide_id is not in the peptide_to_ions map
486            if !self
487                .precursor_frame_builder
488                .peptide_to_ions
489                .contains_key(&peptide_id)
490            {
491                continue;
492            }
493
494            // get all the ions for the peptide
495            let (ion_abundances, scan_occurrences, scan_abundances, charges, spectra) = self
496                .precursor_frame_builder
497                .peptide_to_ions
498                .get(&peptide_id)
499                .unwrap();
500
501            for (index, ion_abundance) in ion_abundances.iter().enumerate() {
502                // occurrence and abundance of the ion in the scan
503                let all_scan_occurrence = scan_occurrences.get(index).unwrap();
504                let all_scan_abundance = scan_abundances.get(index).unwrap();
505
506                // get precursor spectrum for the ion
507                let spectrum = spectra.get(index).unwrap();
508
509                // go over occurrence and abundance of the ion in the scan
510                for (scan, scan_abundance) in
511                    all_scan_occurrence.iter().zip(all_scan_abundance.iter())
512                {
513                    // first, check if precursor is transmitted
514                    if !self.transmission_settings.any_transmitted(
515                        frame_id as i32,
516                        *scan as i32,
517                        &spectrum.mz,
518                        None,
519                    ) {
520                        continue;
521                    }
522
523                    // calculate abundance factor
524                    let total_events = self
525                        .precursor_frame_builder
526                        .peptide_to_events
527                        .get(&peptide_id)
528                        .unwrap();
529                    let fraction_events =
530                        frame_abundance * scan_abundance * ion_abundance * total_events;
531
532                    // get PASEF settings for the given frame
533                    let maybe_pasef_meta = self.transmission_settings.pasef_meta.get(&(frame_id as i32));
534
535                    let collision_energy: f64 = match maybe_pasef_meta {
536                        Some(pasef_meta) => {
537                            pasef_meta
538                                .iter()
539                                .find(|scan_meta| scan_meta.scan_start <= *scan as i32 && scan_meta.scan_end >= *scan as i32)
540                                .map(|s| s.collision_energy)
541                                .unwrap_or(0.0)
542                        },
543                        None => 0.0
544                    };
545
546                    let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
547
548                    // get charge state for the ion
549                    let charge_state = charges.get(index).unwrap();
550                    // extract fragment ions for the peptide, charge state and collision energy
551                    let maybe_value = fragment_ions.get(&(
552                        *peptide_id,
553                        *charge_state,
554                        collision_energy_quantized,
555                    ));
556
557                    // jump to next peptide if the fragment_ions is None (can this happen?)
558                    if maybe_value.is_none() {
559                        continue;
560                    }
561
562                    // for each fragment ion series, create a spectrum and add it to the tims_spectra
563                    for fragment_ion_series in maybe_value.unwrap().1.iter() {
564                        let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
565                        let right_drag = right_drag.unwrap_or(false);
566
567                        let mz_spectrum = if mz_noise_fragment {
568                            match uniform {
569                                true => scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag),
570                                false => scaled_spec.add_mz_noise_normal(fragment_ppm),
571                            }
572                        } else {
573                            scaled_spec
574                        };
575
576                        tims_spectra.push(TimsSpectrum::new(
577                            frame_id as i32,
578                            *scan as i32,
579                            *self
580                                .precursor_frame_builder
581                                .frame_to_rt
582                                .get(&frame_id)
583                                .unwrap() as f64,
584                            *self
585                                .precursor_frame_builder
586                                .scan_to_mobility
587                                .get(&scan)
588                                .unwrap() as f64,
589                            ms_type.clone(),
590                            IndexedMzSpectrum::new(
591                                vec![0; mz_spectrum.mz.len()],
592                                mz_spectrum.mz,
593                                mz_spectrum.intensity,
594                            )
595                                .filter_ranged(100.0, 1700.0, 1.0, 1e9),
596                        ));
597                    }
598                }
599            }
600        }
601
602        if tims_spectra.is_empty() {
603            return TimsFrame::new(
604                frame_id as i32,
605                ms_type.clone(),
606                *self
607                    .precursor_frame_builder
608                    .frame_to_rt
609                    .get(&frame_id)
610                    .unwrap() as f64,
611                vec![],
612                vec![],
613                vec![],
614                vec![],
615                vec![],
616            );
617        }
618
619        let tims_frame = TimsFrame::from_tims_spectra(tims_spectra);
620        tims_frame.filter_ranged(
621            mz_min.unwrap_or(100.0),
622            mz_max.unwrap_or(1700.0),
623            0,
624            1000,
625            0.0,
626            10.0,
627            intensity_min.unwrap_or(1.0),
628            1e9,
629        )
630    }
631
632    pub fn build_fragment_frame_annotated(
633        &self,
634        frame_id: u32,
635        fragment_ions: &BTreeMap<
636            (u32, i8, i32),
637            (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>),
638        >,
639        mz_noise_fragment: bool,
640        uniform: bool,
641        fragment_ppm: f64,
642        mz_min: Option<f64>,
643        mz_max: Option<f64>,
644        intensity_min: Option<f64>,
645        right_drag: Option<bool>,
646    ) -> TimsFrameAnnotated {
647        let ms_type = match self
648            .precursor_frame_builder
649            .precursor_frame_id_set
650            .contains(&frame_id)
651        {
652            false => MsType::FragmentDia,
653            true => MsType::Unknown,
654        };
655
656        let mut tims_spectra: Vec<TimsSpectrumAnnotated> = Vec::new();
657
658        if !self
659            .precursor_frame_builder
660            .frame_to_abundances
661            .contains_key(&frame_id)
662        {
663            return TimsFrameAnnotated::new(
664                frame_id as i32,
665                *self
666                    .precursor_frame_builder
667                    .frame_to_rt
668                    .get(&frame_id)
669                    .unwrap() as f64,
670                ms_type.clone(),
671                vec![],
672                vec![],
673                vec![],
674                vec![],
675                vec![],
676                vec![],
677            );
678        }
679
680        let (peptide_ids, frame_abundances) = self
681            .precursor_frame_builder
682            .frame_to_abundances
683            .get(&frame_id)
684            .unwrap();
685
686        for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
687            if !self
688                .precursor_frame_builder
689                .peptide_to_ions
690                .contains_key(&peptide_id)
691            {
692                continue;
693            }
694
695            let (ion_abundances, scan_occurrences, scan_abundances, charges, _) = self
696                .precursor_frame_builder
697                .peptide_to_ions
698                .get(&peptide_id)
699                .unwrap();
700
701            for (index, ion_abundance) in ion_abundances.iter().enumerate() {
702                let all_scan_occurrence = scan_occurrences.get(index).unwrap();
703                let all_scan_abundance = scan_abundances.get(index).unwrap();
704
705                let peptide = self
706                    .precursor_frame_builder
707                    .peptides
708                    .get(peptide_id)
709                    .unwrap();
710                let ion = PeptideIon::new(
711                    peptide.sequence.sequence.clone(),
712                    charges[index] as i32,
713                    *ion_abundance as f64,
714                    Some(*peptide_id as i32),
715                );
716                // TODO: make this configurable
717                let spectrum = ion.calculate_isotopic_spectrum_annotated(1e-3, 1e-8, 200, 1e-4);
718
719                for (scan, scan_abundance) in
720                    all_scan_occurrence.iter().zip(all_scan_abundance.iter())
721                {
722                    if !self.transmission_settings.any_transmitted(
723                        frame_id as i32,
724                        *scan as i32,
725                        &spectrum.mz,
726                        None,
727                    ) {
728                        continue;
729                    }
730
731                    let total_events = self
732                        .precursor_frame_builder
733                        .peptide_to_events
734                        .get(&peptide_id)
735                        .unwrap();
736                    let fraction_events =
737                        frame_abundance * scan_abundance * ion_abundance * total_events;
738
739                    // get PASEF settings for the given frame
740                    let maybe_pasef_meta = self.transmission_settings.pasef_meta.get(&(frame_id as i32));
741
742                    let collision_energy: f64 = match maybe_pasef_meta {
743                        Some(pasef_meta) => {
744                            pasef_meta
745                                .iter()
746                                .find(|scan_meta| scan_meta.scan_start <= *scan as i32 && scan_meta.scan_end >= *scan as i32)
747                                .map(|s| s.collision_energy)
748                                .unwrap_or(0.0)
749                        },
750                        None => 0.0
751                    };
752
753                    let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
754
755                    let charge_state = charges.get(index).unwrap();
756                    let maybe_value = fragment_ions.get(&(
757                        *peptide_id,
758                        *charge_state,
759                        collision_energy_quantized,
760                    ));
761
762                    if maybe_value.is_none() {
763                        continue;
764                    }
765
766                    for fragment_ion_series in maybe_value.unwrap().1.iter() {
767                        let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
768                        let right_drag = right_drag.unwrap_or(false);
769
770                        let mz_spectrum = if mz_noise_fragment {
771                            match uniform {
772                                true => scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag),
773                                false => scaled_spec.add_mz_noise_normal(fragment_ppm),
774                            }
775                        } else {
776                            scaled_spec
777                        };
778
779                        tims_spectra.push(TimsSpectrumAnnotated::new(
780                            frame_id as i32,
781                            *scan,
782                            *self
783                                .precursor_frame_builder
784                                .frame_to_rt
785                                .get(&frame_id)
786                                .unwrap() as f64,
787                            *self
788                                .precursor_frame_builder
789                                .scan_to_mobility
790                                .get(&scan)
791                                .unwrap() as f64,
792                            ms_type.clone(),
793                            vec![0; mz_spectrum.mz.len()],
794                            mz_spectrum,
795                        ));
796                    }
797                }
798            }
799        }
800
801        if tims_spectra.is_empty() {
802            return TimsFrameAnnotated::new(
803                frame_id as i32,
804                *self
805                    .precursor_frame_builder
806                    .frame_to_rt
807                    .get(&frame_id)
808                    .unwrap() as f64,
809                ms_type.clone(),
810                vec![],
811                vec![],
812                vec![],
813                vec![],
814                vec![],
815                vec![],
816            );
817        }
818
819        let tims_frame = TimsFrameAnnotated::from_tims_spectra_annotated(tims_spectra);
820
821        tims_frame.filter_ranged(
822            mz_min.unwrap_or(100.0),
823            mz_max.unwrap_or(1700.0),
824            0.0,
825            10.0,
826            0,
827            1000,
828            intensity_min.unwrap_or(1.0),
829            1e9,
830        )
831    }
832
833    pub fn get_collision_energy(&self, frame_id: i32, scan_id: i32) -> f64 {
834        self.transmission_settings.get_collision_energy(frame_id, scan_id).unwrap_or(0.0)
835    }
836
837    pub fn get_collision_energies(&self, frame_ids: Vec<i32>, scan_ids: Vec<i32>) -> Vec<f64> {
838        let mut collision_energies: Vec<f64> = Vec::new();
839        for frame_id in frame_ids {
840            for scan_id in &scan_ids {
841                collision_energies.push(self.get_collision_energy(frame_id, *scan_id));
842            }
843        }
844        collision_energies
845    }
846}