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        let mut tims_spectra: Vec<TimsSpectrum> = Vec::new();
449
450        // Frame might not have any peptides
451        if !self
452            .precursor_frame_builder
453            .frame_to_abundances
454            .contains_key(&frame_id)
455        {
456            return TimsFrame::new(
457                frame_id as i32,
458                ms_type.clone(),
459                *self
460                    .precursor_frame_builder
461                    .frame_to_rt
462                    .get(&frame_id)
463                    .unwrap() as f64,
464                vec![],
465                vec![],
466                vec![],
467                vec![],
468                vec![],
469            );
470        }
471
472        // Get the peptide ids and abundances for the frame, should now save to unwrap since we checked if the frame is in the map
473        let (peptide_ids, frame_abundances) = self
474            .precursor_frame_builder
475            .frame_to_abundances
476            .get(&frame_id)
477            .unwrap();
478
479        // Go over all peptides in the frame with their respective abundances
480        for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
481            // jump to next peptide if the peptide_id is not in the peptide_to_ions map
482            if !self
483                .precursor_frame_builder
484                .peptide_to_ions
485                .contains_key(&peptide_id)
486            {
487                continue;
488            }
489
490            // get all the ions for the peptide
491            let (ion_abundances, scan_occurrences, scan_abundances, charges, spectra) = self
492                .precursor_frame_builder
493                .peptide_to_ions
494                .get(&peptide_id)
495                .unwrap();
496
497            for (index, ion_abundance) in ion_abundances.iter().enumerate() {
498                // occurrence and abundance of the ion in the scan
499                let all_scan_occurrence = scan_occurrences.get(index).unwrap();
500                let all_scan_abundance = scan_abundances.get(index).unwrap();
501
502                // get precursor spectrum for the ion
503                let spectrum = spectra.get(index).unwrap();
504
505                // go over occurrence and abundance of the ion in the scan
506                for (scan, scan_abundance) in
507                    all_scan_occurrence.iter().zip(all_scan_abundance.iter())
508                {
509                    // first, check if precursor is transmitted
510                    if !self.transmission_settings.any_transmitted(
511                        frame_id as i32,
512                        *scan as i32,
513                        &spectrum.mz,
514                        None,
515                    ) {
516                        continue;
517                    }
518
519                    // calculate abundance factor
520                    let total_events = self
521                        .precursor_frame_builder
522                        .peptide_to_events
523                        .get(&peptide_id)
524                        .unwrap();
525                    let fraction_events =
526                        frame_abundance * scan_abundance * ion_abundance * total_events;
527
528                    // get PASEF settings for the given frame
529                    let maybe_pasef_meta = self.transmission_settings.pasef_meta.get(&(frame_id as i32));
530
531                    let collision_energy: f64 = match maybe_pasef_meta {
532                        Some(pasef_meta) => {
533                            pasef_meta
534                                .iter()
535                                .find(|scan_meta| scan_meta.scan_start <= *scan as i32 && scan_meta.scan_end >= *scan as i32)
536                                .map(|s| s.collision_energy)
537                                .unwrap_or(0.0)
538                        },
539                        None => 0.0
540                    };
541
542                    let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
543
544                    // get charge state for the ion
545                    let charge_state = charges.get(index).unwrap();
546                    // extract fragment ions for the peptide, charge state and collision energy
547                    let maybe_value = fragment_ions.get(&(
548                        *peptide_id,
549                        *charge_state,
550                        collision_energy_quantized,
551                    ));
552
553                    // jump to next peptide if the fragment_ions is None (can this happen?)
554                    if maybe_value.is_none() {
555                        continue;
556                    }
557
558                    // for each fragment ion series, create a spectrum and add it to the tims_spectra
559                    for fragment_ion_series in maybe_value.unwrap().1.iter() {
560                        let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
561                        let right_drag = right_drag.unwrap_or(false);
562
563                        let mz_spectrum = if mz_noise_fragment {
564                            match uniform {
565                                true => scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag),
566                                false => scaled_spec.add_mz_noise_normal(fragment_ppm),
567                            }
568                        } else {
569                            scaled_spec
570                        };
571
572                        tims_spectra.push(TimsSpectrum::new(
573                            frame_id as i32,
574                            *scan as i32,
575                            *self
576                                .precursor_frame_builder
577                                .frame_to_rt
578                                .get(&frame_id)
579                                .unwrap() as f64,
580                            *self
581                                .precursor_frame_builder
582                                .scan_to_mobility
583                                .get(&scan)
584                                .unwrap() as f64,
585                            ms_type.clone(),
586                            IndexedMzSpectrum::new(
587                                vec![0; mz_spectrum.mz.len()],
588                                mz_spectrum.mz,
589                                mz_spectrum.intensity,
590                            )
591                                .filter_ranged(100.0, 1700.0, 1.0, 1e9),
592                        ));
593                    }
594                }
595            }
596        }
597
598        if tims_spectra.is_empty() {
599            return TimsFrame::new(
600                frame_id as i32,
601                ms_type.clone(),
602                *self
603                    .precursor_frame_builder
604                    .frame_to_rt
605                    .get(&frame_id)
606                    .unwrap() as f64,
607                vec![],
608                vec![],
609                vec![],
610                vec![],
611                vec![],
612            );
613        }
614
615        let tims_frame = TimsFrame::from_tims_spectra(tims_spectra);
616        tims_frame.filter_ranged(
617            mz_min.unwrap_or(100.0),
618            mz_max.unwrap_or(1700.0),
619            0,
620            1000,
621            0.0,
622            10.0,
623            intensity_min.unwrap_or(1.0),
624            1e9,
625        )
626    }
627
628    pub fn build_fragment_frame_annotated(
629        &self,
630        frame_id: u32,
631        fragment_ions: &BTreeMap<
632            (u32, i8, i32),
633            (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>),
634        >,
635        mz_noise_fragment: bool,
636        uniform: bool,
637        fragment_ppm: f64,
638        mz_min: Option<f64>,
639        mz_max: Option<f64>,
640        intensity_min: Option<f64>,
641        right_drag: Option<bool>,
642    ) -> TimsFrameAnnotated {
643        let ms_type = match self
644            .precursor_frame_builder
645            .precursor_frame_id_set
646            .contains(&frame_id)
647        {
648            false => MsType::FragmentDia,
649            true => MsType::Unknown,
650        };
651
652        let mut tims_spectra: Vec<TimsSpectrumAnnotated> = Vec::new();
653
654        if !self
655            .precursor_frame_builder
656            .frame_to_abundances
657            .contains_key(&frame_id)
658        {
659            return TimsFrameAnnotated::new(
660                frame_id as i32,
661                *self
662                    .precursor_frame_builder
663                    .frame_to_rt
664                    .get(&frame_id)
665                    .unwrap() as f64,
666                ms_type.clone(),
667                vec![],
668                vec![],
669                vec![],
670                vec![],
671                vec![],
672                vec![],
673            );
674        }
675
676        let (peptide_ids, frame_abundances) = self
677            .precursor_frame_builder
678            .frame_to_abundances
679            .get(&frame_id)
680            .unwrap();
681
682        for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
683            if !self
684                .precursor_frame_builder
685                .peptide_to_ions
686                .contains_key(&peptide_id)
687            {
688                continue;
689            }
690
691            let (ion_abundances, scan_occurrences, scan_abundances, charges, _) = self
692                .precursor_frame_builder
693                .peptide_to_ions
694                .get(&peptide_id)
695                .unwrap();
696
697            for (index, ion_abundance) in ion_abundances.iter().enumerate() {
698                let all_scan_occurrence = scan_occurrences.get(index).unwrap();
699                let all_scan_abundance = scan_abundances.get(index).unwrap();
700
701                let peptide = self
702                    .precursor_frame_builder
703                    .peptides
704                    .get(peptide_id)
705                    .unwrap();
706                let ion = PeptideIon::new(
707                    peptide.sequence.sequence.clone(),
708                    charges[index] as i32,
709                    *ion_abundance as f64,
710                    Some(*peptide_id as i32),
711                );
712                // TODO: make this configurable
713                let spectrum = ion.calculate_isotopic_spectrum_annotated(1e-3, 1e-8, 200, 1e-4);
714
715                for (scan, scan_abundance) in
716                    all_scan_occurrence.iter().zip(all_scan_abundance.iter())
717                {
718                    if !self.transmission_settings.any_transmitted(
719                        frame_id as i32,
720                        *scan as i32,
721                        &spectrum.mz,
722                        None,
723                    ) {
724                        continue;
725                    }
726
727                    let total_events = self
728                        .precursor_frame_builder
729                        .peptide_to_events
730                        .get(&peptide_id)
731                        .unwrap();
732                    let fraction_events =
733                        frame_abundance * scan_abundance * ion_abundance * total_events;
734
735                    // get PASEF settings for the given frame
736                    let maybe_pasef_meta = self.transmission_settings.pasef_meta.get(&(frame_id as i32));
737
738                    let collision_energy: f64 = match maybe_pasef_meta {
739                        Some(pasef_meta) => {
740                            pasef_meta
741                                .iter()
742                                .find(|scan_meta| scan_meta.scan_start <= *scan as i32 && scan_meta.scan_end >= *scan as i32)
743                                .map(|s| s.collision_energy)
744                                .unwrap_or(0.0)
745                        },
746                        None => 0.0
747                    };
748
749                    let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
750
751                    let charge_state = charges.get(index).unwrap();
752                    let maybe_value = fragment_ions.get(&(
753                        *peptide_id,
754                        *charge_state,
755                        collision_energy_quantized,
756                    ));
757
758                    if maybe_value.is_none() {
759                        continue;
760                    }
761
762                    for fragment_ion_series in maybe_value.unwrap().1.iter() {
763                        let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
764                        let right_drag = right_drag.unwrap_or(false);
765
766                        let mz_spectrum = if mz_noise_fragment {
767                            match uniform {
768                                true => scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag),
769                                false => scaled_spec.add_mz_noise_normal(fragment_ppm),
770                            }
771                        } else {
772                            scaled_spec
773                        };
774
775                        tims_spectra.push(TimsSpectrumAnnotated::new(
776                            frame_id as i32,
777                            *scan,
778                            *self
779                                .precursor_frame_builder
780                                .frame_to_rt
781                                .get(&frame_id)
782                                .unwrap() as f64,
783                            *self
784                                .precursor_frame_builder
785                                .scan_to_mobility
786                                .get(&scan)
787                                .unwrap() as f64,
788                            ms_type.clone(),
789                            vec![0; mz_spectrum.mz.len()],
790                            mz_spectrum,
791                        ));
792                    }
793                }
794            }
795        }
796
797        if tims_spectra.is_empty() {
798            return TimsFrameAnnotated::new(
799                frame_id as i32,
800                *self
801                    .precursor_frame_builder
802                    .frame_to_rt
803                    .get(&frame_id)
804                    .unwrap() as f64,
805                ms_type.clone(),
806                vec![],
807                vec![],
808                vec![],
809                vec![],
810                vec![],
811                vec![],
812            );
813        }
814
815        let tims_frame = TimsFrameAnnotated::from_tims_spectra_annotated(tims_spectra);
816
817        tims_frame.filter_ranged(
818            mz_min.unwrap_or(100.0),
819            mz_max.unwrap_or(1700.0),
820            0.0,
821            10.0,
822            0,
823            1000,
824            intensity_min.unwrap_or(1.0),
825            1e9,
826        )
827    }
828
829    pub fn get_collision_energy(&self, frame_id: i32, scan_id: i32) -> f64 {
830        self.transmission_settings.get_collision_energy(frame_id, scan_id).unwrap_or(0.0)
831    }
832
833    pub fn get_collision_energies(&self, frame_ids: Vec<i32>, scan_ids: Vec<i32>) -> Vec<f64> {
834        let mut collision_energies: Vec<f64> = Vec::new();
835        for frame_id in frame_ids {
836            for scan_id in &scan_ids {
837                collision_energies.push(self.get_collision_energy(frame_id, *scan_id));
838            }
839        }
840        collision_energies
841    }
842}