1use crate::data::acquisition::AcquisitionMode;
2use crate::data::handle::{IndexConverter, TimsData, TimsDataLoader};
3use crate::data::meta::{read_dda_precursor_meta, read_global_meta_sql, read_meta_data_sql, read_pasef_frame_ms_ms_info, DDAPrecursor, DDAPrecursorMeta, PasefMsMsMeta};
4use mscore::timstof::frame::{ImsFrame, RawTimsFrame, TimsFrame};
5use mscore::timstof::slice::TimsSlice;
6use mscore::timstof::spectrum_processing::{
7 PASEFFragmentData, PreprocessedSpectrum, SpectrumProcessingConfig,
8 process_pasef_fragments_batch,
9};
10use rayon::prelude::*;
11use rayon::ThreadPoolBuilder;
12use std::collections::BTreeMap;
13use rand::prelude::IteratorRandom;
14use mscore::data::spectrum::MsType;
15use std::collections::HashMap;
16
17#[derive(Clone)]
18pub struct PASEFDDAFragment {
19 pub frame_id: u32,
20 pub precursor_id: u32,
21 pub collision_energy: f64,
22 pub selected_fragment: TimsFrame,
23}
24
25#[derive(Clone, Debug, Default)]
27pub struct SignalMoments {
28 pub mean: f64,
29 pub variance: f64,
30 pub skewness: f64,
31 pub apex: f64,
32 pub fwhm: f64,
33 pub total_intensity: f64,
34}
35
36impl SignalMoments {
37 pub fn from_signal(coords: &[f64], intensities: &[f64]) -> Self {
39 if coords.is_empty() || intensities.iter().sum::<f64>() == 0.0 {
40 return Self::default();
41 }
42
43 let total: f64 = intensities.iter().sum();
44
45 let mean: f64 = coords.iter()
47 .zip(intensities.iter())
48 .map(|(c, i)| c * i / total)
49 .sum();
50
51 let variance: f64 = coords.iter()
53 .zip(intensities.iter())
54 .map(|(c, i)| i / total * (c - mean).powi(2))
55 .sum();
56
57 let std = variance.sqrt().max(1e-10);
59 let skewness: f64 = coords.iter()
60 .zip(intensities.iter())
61 .map(|(c, i)| i / total * ((c - mean) / std).powi(3))
62 .sum();
63
64 let apex_idx = intensities.iter()
66 .enumerate()
67 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
68 .map(|(i, _)| i)
69 .unwrap_or(0);
70 let apex = coords.get(apex_idx).copied().unwrap_or(0.0);
71
72 let half_max = intensities.get(apex_idx).copied().unwrap_or(0.0) / 2.0;
74 let above_half: Vec<f64> = coords.iter()
75 .zip(intensities.iter())
76 .filter(|(_, i)| **i >= half_max)
77 .map(|(c, _)| *c)
78 .collect();
79 let fwhm = if above_half.len() >= 2 {
80 above_half.last().unwrap_or(&0.0) - above_half.first().unwrap_or(&0.0)
81 } else {
82 2.355 * std };
84
85 SignalMoments {
86 mean,
87 variance,
88 skewness,
89 apex,
90 fwhm,
91 total_intensity: total,
92 }
93 }
94}
95
96#[derive(Clone, Debug)]
98pub struct PrecursorMS1Signal {
99 pub precursor_id: u32,
100
101 pub rt_coords: Vec<f64>, pub rt_intensities: Vec<f64>,
104 pub rt_moments: SignalMoments,
105
106 pub im_coords: Vec<f64>, pub im_intensities: Vec<f64>,
109 pub im_moments: SignalMoments,
110
111 pub isotope_mz: Vec<f64>,
113 pub isotope_intensity: Vec<f64>,
114 pub mz_moments: SignalMoments,
115
116 pub raw_rt: Vec<f64>, pub raw_mz: Vec<f64>, pub raw_mobility: Vec<f64>, pub raw_intensity: Vec<f64>, }
122
123#[derive(Clone, Debug)]
125pub struct PrecursorCoord {
126 pub precursor_id: u32,
127 pub mz: f64, pub mono_mz: f64, pub rt_seconds: f64,
130 pub mobility: f64, pub im_start: f64, pub im_end: f64, pub charge: i32,
134}
135
136pub struct TimsDatasetDDA {
137 pub loader: TimsDataLoader,
138 pub pasef_meta: Vec<PasefMsMsMeta>,
139}
140
141impl TimsDatasetDDA {
142 pub fn new(
143 bruker_lib_path: &str,
144 data_path: &str,
145 in_memory: bool,
146 use_bruker_sdk: bool,
147 ) -> Self {
148 let global_meta_data = read_global_meta_sql(data_path).unwrap();
150 let meta_data = read_meta_data_sql(data_path).unwrap();
151
152 let scan_max_index = meta_data.iter().map(|x| x.num_scans).max().unwrap() as u32;
153 let im_lower = global_meta_data.one_over_k0_range_lower;
154 let im_upper = global_meta_data.one_over_k0_range_upper;
155
156 let tof_max_index = global_meta_data.tof_max_index;
157 let mz_lower = global_meta_data.mz_acquisition_range_lower;
158 let mz_upper = global_meta_data.mz_acquisition_range_upper;
159
160 let loader = match in_memory {
161 true => TimsDataLoader::new_in_memory(
162 bruker_lib_path,
163 data_path,
164 use_bruker_sdk,
165 scan_max_index,
166 im_lower,
167 im_upper,
168 tof_max_index,
169 mz_lower,
170 mz_upper,
171 ),
172 false => TimsDataLoader::new_lazy(
173 bruker_lib_path,
174 data_path,
175 use_bruker_sdk,
176 scan_max_index,
177 im_lower,
178 im_upper,
179 tof_max_index,
180 mz_lower,
181 mz_upper,
182 ),
183 };
184
185 let pasef_meta = read_pasef_frame_ms_ms_info(data_path).unwrap();
186
187 TimsDatasetDDA { loader, pasef_meta }
188 }
189
190 pub fn new_with_calibration(
206 data_path: &str,
207 in_memory: bool,
208 bruker_lib_path: &str,
209 im_lookup: Vec<f64>,
210 ) -> Self {
211 let global_meta_data = read_global_meta_sql(data_path).unwrap();
212
213 let tof_max_index = global_meta_data.tof_max_index;
214 let mz_lower = global_meta_data.mz_acquisition_range_lower;
215 let mz_upper = global_meta_data.mz_acquisition_range_upper;
216
217 let loader = match in_memory {
218 true => TimsDataLoader::new_in_memory_with_calibration(
219 data_path,
220 bruker_lib_path,
221 tof_max_index,
222 mz_lower,
223 mz_upper,
224 im_lookup,
225 ),
226 false => TimsDataLoader::new_lazy_with_calibration(
227 data_path,
228 bruker_lib_path,
229 tof_max_index,
230 mz_lower,
231 mz_upper,
232 im_lookup,
233 ),
234 };
235
236 let pasef_meta = read_pasef_frame_ms_ms_info(data_path).unwrap();
237
238 TimsDatasetDDA { loader, pasef_meta }
239 }
240
241 pub fn new_with_mz_calibration(
255 data_path: &str,
256 in_memory: bool,
257 tof_intercept: f64,
258 tof_slope: f64,
259 ) -> Self {
260 let global_meta_data = read_global_meta_sql(data_path).unwrap();
261 let frame_meta = read_meta_data_sql(data_path).unwrap();
262
263 let scan_max_index = frame_meta.iter().map(|x| x.num_scans).max().unwrap() as u32;
264 let im_lower = global_meta_data.one_over_k0_range_lower;
265 let im_upper = global_meta_data.one_over_k0_range_upper;
266
267 let loader = match in_memory {
268 true => TimsDataLoader::new_in_memory_with_mz_calibration(
269 data_path,
270 tof_intercept,
271 tof_slope,
272 im_lower,
273 im_upper,
274 scan_max_index,
275 ),
276 false => TimsDataLoader::new_lazy_with_mz_calibration(
277 data_path,
278 tof_intercept,
279 tof_slope,
280 im_lower,
281 im_upper,
282 scan_max_index,
283 ),
284 };
285
286 let pasef_meta = read_pasef_frame_ms_ms_info(data_path).unwrap();
287
288 TimsDatasetDDA { loader, pasef_meta }
289 }
290
291 pub fn new_with_bruker_formula(
299 data_path: &str,
300 in_memory: bool,
301 calibration_frame_id: u32,
302 ) -> Self {
303 let loader = match in_memory {
304 true => TimsDataLoader::new_in_memory_with_bruker_formula(
305 data_path,
306 calibration_frame_id,
307 ),
308 false => {
309 TimsDataLoader::new_lazy_with_bruker_formula(data_path, calibration_frame_id)
310 }
311 };
312 let pasef_meta = read_pasef_frame_ms_ms_info(data_path).unwrap();
313 TimsDatasetDDA { loader, pasef_meta }
314 }
315
316 pub fn uses_bruker_sdk(&self) -> bool {
319 self.loader.uses_bruker_sdk()
320 }
321
322 pub fn get_selected_precursors(&self) -> Vec<DDAPrecursor> {
323 let precursor_meta = read_dda_precursor_meta(&self.loader.get_data_path()).unwrap();
324 let pasef_meta = &self.pasef_meta;
325
326 let precursor_id_to_pasef_meta: BTreeMap<i64, &PasefMsMsMeta> = pasef_meta
327 .iter()
328 .map(|x| (x.precursor_id as i64, x))
329 .collect();
330
331 let result: Vec<_> = precursor_meta
333 .iter()
334 .map(|precursor| {
335 let pasef_meta = precursor_id_to_pasef_meta
336 .get(&precursor.precursor_id)
337 .unwrap();
338 DDAPrecursor {
339 frame_id: precursor.precursor_frame_id,
340 precursor_id: precursor.precursor_id,
341 mono_mz: precursor.precursor_mz_monoisotopic,
342 highest_intensity_mz: precursor.precursor_mz_highest_intensity,
343 average_mz: precursor.precursor_mz_average,
344 charge: precursor.precursor_charge,
345 inverse_ion_mobility: self.scan_to_inverse_mobility(
346 precursor.precursor_frame_id as u32,
347 &vec![precursor.precursor_average_scan_number as u32],
348 )[0],
349 collision_energy: pasef_meta.collision_energy,
350 precuror_total_intensity: precursor.precursor_total_intensity,
351 isolation_mz: pasef_meta.isolation_mz,
352 isolation_width: pasef_meta.isolation_width,
353 }
354 })
355 .collect();
356
357 result
358 }
359
360 pub fn get_precursor_frames(
361 &self,
362 min_intensity: f64,
363 max_num_peaks: usize,
364 num_threads: usize,
365 ) -> Vec<TimsFrame> {
366 let meta_data = read_meta_data_sql(&self.loader.get_data_path()).unwrap();
368
369 let precursor_frames = meta_data.iter().filter(|x| x.ms_ms_type == 0);
371
372 let tims_silce =
373 self.get_slice(precursor_frames.map(|x| x.id as u32).collect(), num_threads);
374
375 let result: Vec<_> = tims_silce
376 .frames
377 .par_iter()
378 .map(|frame| {
379 frame
380 .filter_ranged(0.0, 2000.0, 0, 2000, 0.0, 5.0, min_intensity, 1e9, 0, i32::MAX)
381 .top_n(max_num_peaks)
382 })
383 .collect();
384
385 result
386 }
387
388 pub fn extract_precursor_ms1_signals(
409 &self,
410 precursor_coords: Vec<PrecursorCoord>,
411 rt_window_sec: f64,
412 mz_tol_ppm: f64,
413 im_window: f64,
414 n_isotopes: usize,
415 num_threads: usize,
416 ) -> Vec<PrecursorMS1Signal> {
417 if precursor_coords.is_empty() {
418 return Vec::new();
419 }
420
421 let meta_data = read_meta_data_sql(&self.loader.get_data_path()).unwrap();
423
424 let mut ms1_frame_info: Vec<(u32, f64)> = meta_data
426 .iter()
427 .filter(|f| f.ms_ms_type == 0)
428 .map(|f| (f.id as u32, f.time))
429 .collect();
430 ms1_frame_info.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
431
432 let ms1_times: Vec<f64> = ms1_frame_info.iter().map(|(_, t)| *t).collect();
433
434 let mut sorted_coords: Vec<(usize, &PrecursorCoord)> = precursor_coords
436 .iter()
437 .enumerate()
438 .collect();
439 sorted_coords.sort_by(|a, b| a.1.rt_seconds.partial_cmp(&b.1.rt_seconds).unwrap());
440
441 let batch_size_sec = 300.0;
443 let mut results: Vec<(usize, PrecursorMS1Signal)> = Vec::with_capacity(precursor_coords.len());
444
445 let mut batch_start = 0;
446 while batch_start < sorted_coords.len() {
447 let batch_rt_start = sorted_coords[batch_start].1.rt_seconds;
449 let batch_rt_end = batch_rt_start + batch_size_sec;
450
451 let mut batch_end = batch_start;
452 while batch_end < sorted_coords.len() && sorted_coords[batch_end].1.rt_seconds < batch_rt_end {
453 batch_end += 1;
454 }
455
456 let frame_rt_min = batch_rt_start - rt_window_sec;
458 let frame_rt_max = batch_rt_end + rt_window_sec;
459
460 let frame_start_idx = ms1_times.partition_point(|t| *t < frame_rt_min);
461 let frame_end_idx = ms1_times.partition_point(|t| *t <= frame_rt_max);
462
463 let batch_frame_ids: Vec<u32> = ms1_frame_info[frame_start_idx..frame_end_idx]
465 .iter()
466 .map(|(id, _)| *id)
467 .collect();
468
469 let batch_frames = if !batch_frame_ids.is_empty() {
470 self.loader.get_slice(batch_frame_ids, num_threads)
471 } else {
472 TimsSlice { frames: Vec::new() }
473 };
474
475 let batch_times: Vec<f64> = ms1_times[frame_start_idx..frame_end_idx].to_vec();
476
477 let batch_coords = &sorted_coords[batch_start..batch_end];
479
480 let pool = ThreadPoolBuilder::new()
481 .num_threads(num_threads)
482 .build()
483 .unwrap();
484
485 let batch_results: Vec<(usize, PrecursorMS1Signal)> = pool.install(|| {
486 batch_coords.par_iter().map(|(orig_idx, coord)| {
487 let signal = Self::extract_single_precursor(
488 coord,
489 &batch_frames.frames,
490 &batch_times,
491 rt_window_sec,
492 mz_tol_ppm,
493 im_window,
494 n_isotopes,
495 );
496 (*orig_idx, signal)
497 }).collect()
498 });
499
500 results.extend(batch_results);
501 batch_start = batch_end;
502 }
503
504 results.sort_by_key(|(idx, _)| *idx);
506 results.into_iter().map(|(_, signal)| signal).collect()
507 }
508
509 fn extract_single_precursor(
511 coord: &PrecursorCoord,
512 frames: &[TimsFrame],
513 frame_times: &[f64],
514 rt_window_sec: f64,
515 mz_tol_ppm: f64,
516 im_window: f64,
517 n_isotopes: usize,
518 ) -> PrecursorMS1Signal {
519 let rt_sec = coord.rt_seconds;
520
521 let rt_min = rt_sec - rt_window_sec / 2.0;
523 let rt_max = rt_sec + rt_window_sec / 2.0;
524
525 let start_idx = frame_times.partition_point(|t| *t < rt_min);
526 let end_idx = frame_times.partition_point(|t| *t <= rt_max);
527
528 let isotope_spacing = 1.003355 / (coord.charge.max(1) as f64);
530 let n_isotopes_to_extract = 4.min(n_isotopes); let has_mono_mz = coord.mono_mz > 0.0;
536 let base_mz = if has_mono_mz { coord.mono_mz } else { coord.mz };
537 let mz_tol = base_mz * mz_tol_ppm / 1e6;
538
539 let isotope_mz_values: Vec<f64> = (0..n_isotopes_to_extract)
541 .map(|i| base_mz + (i as f64) * isotope_spacing)
542 .collect();
543
544 let (xic_mz_min, xic_mz_max) = if has_mono_mz {
548 (base_mz - mz_tol, base_mz + ((n_isotopes_to_extract - 1) as f64) * isotope_spacing + mz_tol)
550 } else {
551 (coord.mz - mz_tol, coord.mz + mz_tol)
553 };
554
555 let im_min = coord.mobility - im_window / 2.0;
557 let im_max = coord.mobility + im_window / 2.0;
558
559 let n_frames = end_idx.saturating_sub(start_idx);
561 let mut rt_coords = Vec::with_capacity(n_frames);
562 let mut rt_intensities = Vec::with_capacity(n_frames);
563 let mut im_dict: HashMap<i64, f64> = HashMap::new();
564 let mut isotope_intensity = vec![0.0f64; n_isotopes]; let mut raw_rt = Vec::new();
568 let mut raw_mz = Vec::new();
569 let mut raw_mobility = Vec::new();
570 let mut raw_intensity = Vec::new();
571
572 for idx in start_idx..end_idx.min(frames.len()) {
574 let frame_time = frame_times[idx];
575 let frame = &frames[idx];
576
577 let xic_filtered = frame.filter_ranged(
583 xic_mz_min, xic_mz_max,
584 0, i32::MAX,
585 im_min, im_max,
586 0.0, 1e9,
587 0, i32::MAX,
588 );
589
590 let xic_intensity: f64 = xic_filtered.ims_frame.intensity.iter().sum();
592 rt_coords.push(frame_time);
593 rt_intensities.push(xic_intensity);
594
595 for (mob, inten) in xic_filtered.ims_frame.mobility.iter().zip(xic_filtered.ims_frame.intensity.iter()) {
597 let mob_bin = (*mob * 1000.0).round() as i64;
598 *im_dict.entry(mob_bin).or_insert(0.0) += *inten;
599 }
600
601 for (iso_idx, iso_mz) in isotope_mz_values.iter().enumerate() {
604 let iso_peak_min = iso_mz - mz_tol;
605 let iso_peak_max = iso_mz + mz_tol;
606 let iso_intensity_sum: f64 = xic_filtered.ims_frame.mz.iter()
607 .zip(xic_filtered.ims_frame.intensity.iter())
608 .filter(|(mz, _)| **mz >= iso_peak_min && **mz <= iso_peak_max)
609 .map(|(_, i)| *i)
610 .sum();
611 isotope_intensity[iso_idx] += iso_intensity_sum;
612 }
613
614 let n_peaks = xic_filtered.ims_frame.mz.len();
616 for i in 0..n_peaks {
617 raw_rt.push(frame_time);
618 raw_mz.push(xic_filtered.ims_frame.mz[i]);
619 raw_mobility.push(xic_filtered.ims_frame.mobility[i]);
620 raw_intensity.push(xic_filtered.ims_frame.intensity[i]);
621 }
622 }
623
624 let mut im_entries: Vec<(i64, f64)> = im_dict.into_iter().collect();
626 im_entries.sort_by_key(|(k, _)| *k);
627 let im_coords: Vec<f64> = im_entries.iter().map(|(k, _)| *k as f64 / 1000.0).collect();
628 let im_intensities: Vec<f64> = im_entries.iter().map(|(_, v)| *v).collect();
629
630 let rt_moments = SignalMoments::from_signal(&rt_coords, &rt_intensities);
632 let im_moments = SignalMoments::from_signal(&im_coords, &im_intensities);
633 let mz_moments = SignalMoments::from_signal(&isotope_mz_values, &isotope_intensity);
634
635 PrecursorMS1Signal {
636 precursor_id: coord.precursor_id,
637 rt_coords,
638 rt_intensities,
639 rt_moments,
640 im_coords,
641 im_intensities,
642 im_moments,
643 isotope_mz: isotope_mz_values,
644 isotope_intensity,
645 mz_moments,
646 raw_rt,
647 raw_mz,
648 raw_mobility,
649 raw_intensity,
650 }
651 }
652
653 pub fn get_pasef_frame_ms_ms_info(&self) -> Vec<PasefMsMsMeta> {
654 read_pasef_frame_ms_ms_info(&self.loader.get_data_path()).unwrap()
655 }
656
657 pub fn get_pasef_fragments(&self, num_threads: usize) -> Vec<PASEFDDAFragment> {
659 self.get_pasef_fragments_for_precursors(None, num_threads)
661 }
662
663 pub fn get_pasef_fragments_for_precursors(
667 &self,
668 precursor_ids: Option<&[u32]>,
669 num_threads: usize,
670 ) -> Vec<PASEFDDAFragment> {
671 let pasef_info = self.get_pasef_frame_ms_ms_info();
673
674 let filtered_pasef_info: Vec<&PasefMsMsMeta> = match precursor_ids {
676 Some(ids) => {
677 let id_set: std::collections::HashSet<u32> = ids.iter().copied().collect();
679 pasef_info.iter()
680 .filter(|info| id_set.contains(&(info.precursor_id as u32)))
681 .collect()
682 }
683 None => pasef_info.iter().collect(),
684 };
685
686 let sdk_free = self.loader.sdk_free_converter();
692 let conv: Option<&(dyn IndexConverter + Sync)> =
693 sdk_free.as_ref().map(|c| c as &(dyn IndexConverter + Sync));
694 let read_frame = |frame_id: u32| match conv {
695 Some(c) => self.loader.get_frame_with_converter(frame_id, c),
696 None => self.loader.get_frame(frame_id),
697 };
698 let force_serial = self.loader.uses_bruker_sdk() && conv.is_none();
699
700 let process_fragment = |pasef_info: &PasefMsMsMeta| -> PASEFDDAFragment {
702 let frame = read_frame(pasef_info.frame_id as u32);
704
705 let scan_margin = (pasef_info.scan_num_end - pasef_info.scan_num_begin) / 20;
707
708 let filtered_frame = frame.filter_ranged(
710 0.0,
711 2000.0,
712 (pasef_info.scan_num_begin - scan_margin) as i32,
713 (pasef_info.scan_num_end + scan_margin) as i32,
714 0.0,
715 5.0,
716 0.0,
717 1e9,
718 0,
719 i32::MAX,
720 );
721
722 PASEFDDAFragment {
723 frame_id: pasef_info.frame_id as u32,
724 precursor_id: pasef_info.precursor_id as u32,
725 collision_energy: pasef_info.collision_energy,
726 selected_fragment: filtered_frame,
728 }
729 };
730
731 if force_serial {
732 filtered_pasef_info.iter().map(|info| process_fragment(info)).collect()
734 } else {
735 let pool = ThreadPoolBuilder::new()
737 .num_threads(num_threads)
738 .build()
739 .unwrap();
740
741 pool.install(|| {
742 filtered_pasef_info.par_iter().map(|info| process_fragment(info)).collect()
743 })
744 }
745 }
746
747 pub fn get_preprocessed_pasef_fragments(
762 &self,
763 dataset_name: &str,
764 config: SpectrumProcessingConfig,
765 num_threads: usize,
766 ) -> Vec<PreprocessedSpectrum> {
767 let pasef_info = self.get_pasef_frame_ms_ms_info();
769
770 let precursor_meta = read_dda_precursor_meta(&self.loader.get_data_path()).unwrap_or_default();
772 let frame_meta = read_meta_data_sql(&self.loader.get_data_path()).unwrap_or_default();
773
774 let precursor_map: BTreeMap<i64, &DDAPrecursorMeta> = precursor_meta
776 .iter()
777 .map(|p| (p.precursor_id, p))
778 .collect();
779
780 let frame_time_map: BTreeMap<i64, f64> = frame_meta
781 .iter()
782 .map(|f| (f.id, f.time / 60.0)) .collect();
784
785 let mut pasef_by_precursor: BTreeMap<i64, Vec<&PasefMsMsMeta>> = BTreeMap::new();
788 for info in &pasef_info {
789 pasef_by_precursor
790 .entry(info.precursor_id)
791 .or_insert_with(Vec::new)
792 .push(info);
793 }
794
795 let sdk_free = self.loader.sdk_free_converter();
802 let conv: Option<&(dyn IndexConverter + Sync)> =
803 sdk_free.as_ref().map(|c| c as &(dyn IndexConverter + Sync));
804 let read_frame = |frame_id: u32| match conv {
805 Some(c) => self.loader.get_frame_with_converter(frame_id, c),
806 None => self.loader.get_frame(frame_id),
807 };
808 let force_serial = self.loader.uses_bruker_sdk() && conv.is_none();
809
810 let process_precursor = |(precursor_id, pasef_infos): (&i64, &Vec<&PasefMsMsMeta>)| -> Option<PASEFFragmentData> {
812 let precursor = precursor_map.get(precursor_id)?;
814
815 let first_pasef = pasef_infos.first()?;
817
818 let scan_start_time = frame_time_map.get(&first_pasef.frame_id).copied().unwrap_or(0.0);
820
821 let mut combined_scan = Vec::new();
823 let mut combined_mobility = Vec::new();
824 let mut combined_tof = Vec::new();
825 let mut combined_mz = Vec::new();
826 let mut combined_intensity = Vec::new();
827
828 for pasef_info in pasef_infos {
829 let frame = read_frame(pasef_info.frame_id as u32);
831
832 let scan_margin = (pasef_info.scan_num_end - pasef_info.scan_num_begin) / 20;
834
835 let filtered_frame = frame.filter_ranged(
837 0.0,
838 2000.0,
839 (pasef_info.scan_num_begin - scan_margin) as i32,
840 (pasef_info.scan_num_end + scan_margin) as i32,
841 0.0,
842 5.0,
843 0.0,
844 1e9,
845 0,
846 i32::MAX,
847 );
848
849 combined_scan.extend(filtered_frame.scan.iter());
851 combined_mobility.extend(filtered_frame.ims_frame.mobility.iter());
852 combined_tof.extend(filtered_frame.tof.iter());
853 combined_mz.extend(filtered_frame.ims_frame.mz.iter());
854 combined_intensity.extend(filtered_frame.ims_frame.intensity.iter());
855 }
856
857 if combined_mz.is_empty() {
858 return None;
859 }
860
861 let precursor_mz = precursor.precursor_mz_monoisotopic
863 .unwrap_or(precursor.precursor_mz_highest_intensity);
864
865 Some(PASEFFragmentData {
866 frame_id: first_pasef.frame_id as u32,
867 precursor_id: *precursor_id as u32,
868 collision_energy: first_pasef.collision_energy,
869 scan_start_time,
870 scan: combined_scan,
871 mobility: combined_mobility,
872 tof: combined_tof,
873 mz: combined_mz,
874 intensity: combined_intensity,
875 precursor_mz,
876 precursor_charge: precursor.precursor_charge.map(|c| c as i32),
877 precursor_intensity: precursor.precursor_total_intensity,
878 isolation_mz: first_pasef.isolation_mz,
879 isolation_width: first_pasef.isolation_width,
880 })
881 };
882
883 let fragment_data: Vec<PASEFFragmentData> = if force_serial {
884 pasef_by_precursor
886 .iter()
887 .filter_map(process_precursor)
888 .collect()
889 } else {
890 let pool = ThreadPoolBuilder::new()
892 .num_threads(num_threads)
893 .build()
894 .unwrap();
895
896 pool.install(|| {
897 pasef_by_precursor
898 .par_iter()
899 .filter_map(|item| process_precursor(item))
900 .collect()
901 })
902 };
903
904 process_pasef_fragments_batch(fragment_data, dataset_name, &config, num_threads)
906 }
907
908 pub fn sample_pasef_fragment_random(
909 &self,
910 target_scan_apex: i32,
911 experiment_max_scan: i32,
912 ) -> TimsFrame {
913 let pasef_meta = &self.pasef_meta;
914 let random_index = rand::random::<usize>() % pasef_meta.len();
915 let pasef_info = &pasef_meta[random_index];
916
917 let frame = self.loader.get_frame(pasef_info.frame_id as u32);
919
920 let scan_margin = (pasef_info.scan_num_end - pasef_info.scan_num_begin) / 20;
922
923 let mut filtered = frame.filter_ranged(
925 0.0,
926 2000.0,
927 (pasef_info.scan_num_begin - scan_margin) as i32,
928 (pasef_info.scan_num_end + scan_margin) as i32,
929 0.0,
930 5.0,
931 0.0,
932 1e9,
933 0,
934 i32::MAX,
935 );
936
937 if filtered.scan.is_empty() {
939 return filtered;
940 }
941
942 let mut scan_copy = filtered.scan.clone();
944 scan_copy.sort_unstable();
945 let median_scan = scan_copy[scan_copy.len() / 2];
946
947 let scan_shift = target_scan_apex - median_scan;
949
950 for s in filtered.scan.iter_mut() {
952 *s += scan_shift;
953 }
954
955 let re_filtered = filtered.filter_ranged(
957 0.0,
958 2000.0,
959 0,
960 experiment_max_scan,
961 0.0,
962 5.0,
963 0.0,
964 1e9,
965 0,
966 i32::MAX,
967 );
968
969 re_filtered
970 }
971
972 pub fn sample_pasef_fragments_random(
973 &self,
974 target_scan_apex_values: Vec<i32>,
975 experiment_max_scan: i32,
976 ) -> TimsFrame {
977
978 if target_scan_apex_values.is_empty() {
980 return TimsFrame {
981 frame_id: 0, ms_type: MsType::FragmentDda,
983 scan: Vec::new(),
984 tof: Vec::new(),
985 ims_frame: ImsFrame::default(), }
987 }
988
989 let mut pasef_frames = Vec::new();
990
991 for target_scan_apex in target_scan_apex_values {
992 let pasef_frame = self.sample_pasef_fragment_random(target_scan_apex, experiment_max_scan);
993 pasef_frames.push(pasef_frame);
994 }
995
996 let mut combined_frame = pasef_frames[0].clone();
998
999 for frame in pasef_frames.iter().skip(1) {
1000 combined_frame = combined_frame + frame.clone();
1001 }
1002
1003 let im_values = self.scan_to_inverse_mobility(
1005 combined_frame.frame_id as u32,
1006 &combined_frame.scan.iter().map(|x| *x as u32).collect(),
1007 );
1008
1009 combined_frame.ims_frame.mobility = std::sync::Arc::new(im_values);
1011
1012 combined_frame
1013 }
1014
1015 pub fn sample_precursor_signal(
1016 &self,
1017 num_frames: usize,
1018 max_intensity: f64,
1019 take_probability: f64,
1020 ) -> TimsFrame {
1021 let meta_data = read_meta_data_sql(&self.loader.get_data_path()).unwrap();
1023 let precursor_frames = meta_data.iter().filter(|x| x.ms_ms_type == 0);
1024
1025 let mut rng = rand::thread_rng();
1027 let mut sampled_frames: Vec<TimsFrame> = Vec::new();
1028
1029 for frame in precursor_frames.choose_multiple(&mut rng, num_frames) {
1031 let frame_id = frame.id;
1032 let frame_data = self
1033 .loader
1034 .get_frame(frame_id as u32)
1035 .filter_ranged(0.0, 2000.0, 0, 1000, 0.0, 5.0, 1.0, max_intensity, 0, i32::MAX)
1036 .generate_random_sample(take_probability);
1037 sampled_frames.push(frame_data);
1038 }
1039
1040 let mut sampled_frame = sampled_frames.remove(0);
1042
1043 for frame in sampled_frames {
1045 sampled_frame = sampled_frame + frame;
1046 }
1047
1048 sampled_frame
1049 }
1050}
1051
1052impl TimsData for TimsDatasetDDA {
1053 fn get_frame(&self, frame_id: u32) -> TimsFrame {
1054 self.loader.get_frame(frame_id)
1055 }
1056
1057 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
1058 self.loader.get_raw_frame(frame_id)
1059 }
1060
1061 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1062 self.loader.get_slice(frame_ids, num_threads)
1063 }
1064
1065 fn get_acquisition_mode(&self) -> AcquisitionMode {
1066 self.loader.get_acquisition_mode().clone()
1067 }
1068
1069 fn get_frame_count(&self) -> i32 {
1070 self.loader.get_frame_count()
1071 }
1072
1073 fn get_data_path(&self) -> &str {
1074 &self.loader.get_data_path()
1075 }
1076}
1077
1078impl IndexConverter for TimsDatasetDDA {
1079 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1080 self.loader
1081 .get_index_converter()
1082 .tof_to_mz(frame_id, tof_values)
1083 }
1084
1085 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1086 self.loader
1087 .get_index_converter()
1088 .mz_to_tof(frame_id, mz_values)
1089 }
1090
1091 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1092 self.loader
1093 .get_index_converter()
1094 .scan_to_inverse_mobility(frame_id, scan_values)
1095 }
1096
1097 fn inverse_mobility_to_scan(
1098 &self,
1099 frame_id: u32,
1100 inverse_mobility_values: &Vec<f64>,
1101 ) -> Vec<u32> {
1102 self.loader
1103 .get_index_converter()
1104 .inverse_mobility_to_scan(frame_id, inverse_mobility_values)
1105 }
1106}