1use std::sync::Arc;
2use crate::data::acquisition::AcquisitionMode;
3use rustc_hash::FxHashMap;
4use crate::data::handle::{IndexConverter, TimsData, TimsDataLoader};
5use crate::data::meta::{
6 read_dia_ms_ms_info, read_dia_ms_ms_windows, read_global_meta_sql, read_meta_data_sql,
7 DiaMsMisInfo, DiaMsMsWindow, FrameMeta, GlobalMetaData,
8};
9use mscore::data::spectrum::MsType;
10use mscore::timstof::frame::{RawTimsFrame, TimsFrame};
11use mscore::timstof::slice::TimsSlice;
12use rayon::iter::IntoParallelRefIterator;
13use crate::cluster::peak::{build_frame_bin_view, build_tof_rt_grid_full, expand_many_im_peaks_along_rt, FrameBinView, ImPeak1D, RtExpandParams, RtFrames, TofRtGrid};
14use crate::cluster::utility::{TofScale};
15use crate::data::utility::merge_ranges;
16use rayon::prelude::*;
17use std::collections::{HashMap};
18use rayon::ThreadPoolBuilder;
19use crate::cluster::candidates::{build_pseudo_spectra_all_pairs, build_pseudo_spectra_end_to_end, build_pseudo_spectra_end_to_end_xic, PseudoBuildResult, ScoreOpts};
20use crate::cluster::cluster::{attach_raw_points_for_spec_1d_in_ctx, bin_range_for_win, build_scan_slices, decorate_with_mz_for_cluster, evaluate_spec_1d, make_specs_from_im_and_rt_groups_threads, BuildSpecOpts, ClusterResult1D, ClusterSpec1D, Eval1DOpts, RawAttachContext, RawPoints, ScanSlice};
21use crate::cluster::feature::SimpleFeature;
22use crate::cluster::pseudo::{PseudoSpecOpts};
23use crate::cluster::candidates::CandidateOpts;
24use crate::cluster::scoring::XicScoreOpts;
25
26#[derive(Clone, Debug)]
32pub struct ClusterExtractionOpts {
33 pub tof_step: i32,
35 pub rt_params: RtExpandParams,
37 pub build_opts: BuildSpecOpts,
39 pub eval_opts: Eval1DOpts,
41 pub require_rt_overlap: bool,
43 pub num_threads: usize,
45}
46
47impl Default for ClusterExtractionOpts {
48 fn default() -> Self {
49 Self {
50 tof_step: 8,
51 rt_params: RtExpandParams::default(),
52 build_opts: BuildSpecOpts::ms1_defaults(),
53 eval_opts: Eval1DOpts::default(),
54 require_rt_overlap: true,
55 num_threads: 4,
56 }
57 }
58}
59
60impl ClusterExtractionOpts {
61 pub fn for_ms1() -> Self {
63 Self {
64 build_opts: BuildSpecOpts::ms1_defaults(),
65 ..Self::default()
66 }
67 }
68
69 pub fn for_ms2() -> Self {
71 Self {
72 build_opts: BuildSpecOpts::ms2_defaults(),
73 ..Self::default()
74 }
75 }
76
77 pub fn with_tof_step(mut self, step: i32) -> Self {
79 self.tof_step = step;
80 self
81 }
82
83 pub fn with_threads(mut self, n: usize) -> Self {
85 self.num_threads = n;
86 self
87 }
88}
89
90#[derive(Clone, Debug)]
92pub struct PseudoSpectrumBuildOpts<'a> {
93 pub cand_opts: &'a CandidateOpts,
95 pub score_opts: &'a ScoreOpts,
97 pub pseudo_opts: &'a PseudoSpecOpts,
99 pub features: Option<&'a [SimpleFeature]>,
101}
102
103#[derive(Clone, Debug)]
104pub struct ProgramSlice {
105 pub mz_lo: f64,
106 pub mz_hi: f64,
107 pub scan_lo: u32,
108 pub scan_hi: u32,
109}
110
111#[inline]
112fn ranges_overlap_u32(a: (u32, u32), b: (u32, u32)) -> bool {
113 let lo = a.0.max(b.0);
114 let hi = a.1.min(b.1);
115 hi >= lo
116}
117
118#[derive(Debug, Clone)]
119pub struct Ms2GroupProgram {
120 pub mz_windows: Vec<(f32, f32)>,
122 pub scan_ranges: Vec<(u32, u32)>,
124 pub mz_union: Option<(f32, f32)>,
126 pub scan_unions: Vec<(u32, u32)>,
128}
129
130#[derive(Debug, Clone)]
131pub struct DiaIndex {
132 pub frame_to_group: HashMap<u32, u32>,
134 pub group_to_frames: HashMap<u32, Vec<u32>>,
136 pub group_to_isolation: HashMap<u32, Vec<(f64, f64)>>,
138 pub group_to_scan_ranges: HashMap<u32, Vec<(u32, u32)>>,
140 pub group_to_mz_union: HashMap<u32, (f64, f64)>,
142 pub group_to_scan_unions: HashMap<u32, Vec<(u32, u32)>>,
144 pub frame_time: HashMap<u32, f64>,
146 pub group_to_slices: HashMap<u32, Arc<[ProgramSlice]>>,
148}
149
150impl DiaIndex {
151 pub fn new(meta: &[FrameMeta], info: &[DiaMsMisInfo], wins: &[DiaMsMsWindow]) -> Self {
152 #[inline]
154 fn norm_f64_pair(a: f64, b: f64) -> Option<(f64, f64)> {
155 if !a.is_finite() || !b.is_finite() { return None; }
156 let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
157 if hi > lo { Some((lo, hi)) } else { None }
158 }
159 #[inline]
160 fn norm_u32_pair(a: u32, b: u32) -> Option<(u32, u32)> {
161 let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
162 Some((lo, hi))
163 }
164 fn merge_scan_ranges(mut ranges: Vec<(u32, u32)>) -> Vec<(u32, u32)> {
165 if ranges.is_empty() { return ranges; }
166 ranges.sort_unstable_by_key(|&(l, r)| (l, r));
167 let mut out: Vec<(u32,u32)> = Vec::with_capacity(ranges.len());
168 let mut cur = ranges[0];
169 for &(l, r) in &ranges[1..] {
170 if l <= cur.1.saturating_add(1) {
171 if r > cur.1 { cur.1 = r; }
172 } else {
173 out.push(cur);
174 cur = (l, r);
175 }
176 }
177 out.push(cur);
178 out
179 }
180
181 let mut frame_time: HashMap<u32, f64> = HashMap::new();
183 for m in meta {
184 frame_time.insert(m.id as u32, m.time);
185 }
186
187 let mut frame_to_group: HashMap<u32, u32> = HashMap::new();
189 let mut group_to_frames: HashMap<u32, Vec<u32>> = HashMap::new();
190 for r in info {
191 let fid = r.frame_id;
192 frame_to_group.insert(fid, r.window_group);
193 group_to_frames.entry(r.window_group).or_default().push(fid);
194 }
195
196 let mut group_to_isolation: HashMap<u32, Vec<(f64, f64)>> = HashMap::new();
198 let mut group_to_scan_ranges: HashMap<u32, Vec<(u32, u32)>> = HashMap::new();
199 for w in wins {
200 let half = 0.5 * w.isolation_width;
201 if half.is_finite() && half > 0.0 && w.isolation_mz.is_finite() {
202 let lo = w.isolation_mz - half;
203 let hi = w.isolation_mz + half;
204 if let Some(p) = norm_f64_pair(lo, hi) {
205 group_to_isolation.entry(w.window_group).or_default().push(p);
206 }
207 }
208 if let Some(p) = norm_u32_pair(w.scan_num_begin, w.scan_num_end) {
209 group_to_scan_ranges.entry(w.window_group).or_default().push(p);
210 }
211 }
212
213 let mut group_to_mz_union: HashMap<u32, (f64, f64)> = HashMap::new();
215 let mut group_to_scan_unions: HashMap<u32, Vec<(u32, u32)>> = HashMap::new();
216
217 for (g, frames) in group_to_frames.iter_mut() {
218 frames.sort_unstable_by(|&a, &b| {
219 let ta = frame_time.get(&a).copied().unwrap_or(f64::NAN);
220 let tb = frame_time.get(&b).copied().unwrap_or(f64::NAN);
221 ta.partial_cmp(&tb).unwrap_or(std::cmp::Ordering::Equal)
222 });
223
224 if let Some(v) = group_to_isolation.get(g) {
225 if !v.is_empty() {
226 let lo = v.iter().map(|p| p.0).fold(f64::INFINITY, f64::min);
227 let hi = v.iter().map(|p| p.1).fold(f64::NEG_INFINITY, f64::max);
228 if lo.is_finite() && hi.is_finite() && hi > lo {
229 group_to_mz_union.insert(*g, (lo, hi));
230 }
231 }
232 }
233 let merged = merge_scan_ranges(group_to_scan_ranges.get(g).cloned().unwrap_or_default());
234 group_to_scan_unions.insert(*g, merged);
235 }
236
237 let mut group_to_slices: HashMap<u32, Arc<[ProgramSlice]>> = HashMap::new();
238
239 for (&g, iso_rows) in &group_to_isolation {
240 let scan_rows = group_to_scan_ranges.get(&g).cloned().unwrap_or_default();
241 let n = iso_rows.len().min(scan_rows.len());
242 let mut v = Vec::with_capacity(n);
243 for k in 0..n {
244 let (mz_lo, mz_hi) = iso_rows[k];
245 let (scan_lo, scan_hi) = scan_rows[k];
246 v.push(ProgramSlice { mz_lo, mz_hi, scan_lo, scan_hi });
247 }
248 group_to_slices.insert(g, v.into());
249 }
250
251 DiaIndex {
252 frame_to_group,
253 group_to_frames,
254 group_to_isolation,
255 group_to_scan_ranges,
256 group_to_mz_union,
257 group_to_scan_unions,
258 frame_time,
259 group_to_slices,
260 }
261 }
262
263 pub fn program_slices_for_group(&self, g: u32) -> Vec<ProgramSlice> {
264 self.slices_for_group(g).to_vec()
265 }
266
267 pub fn tiles_for_precursor_in_group(
278 &self,
279 g: u32,
280 prec_mz: f32,
281 im_apex: f32,
282 ) -> Vec<usize> {
283 let slices = self.slices_for_group(g);
284 let mut hits = Vec::new();
285
286 if !prec_mz.is_finite() || !im_apex.is_finite() {
287 return hits;
288 }
289
290 for (idx, s) in slices.iter().enumerate() {
291 if prec_mz < s.mz_lo as f32 || prec_mz > s.mz_hi as f32 {
293 continue;
294 }
295
296 let scan_lo = s.scan_lo as f32;
298 let scan_hi = s.scan_hi as f32;
299 if im_apex < scan_lo || im_apex > scan_hi {
300 continue;
301 }
302
303 hits.push(idx);
304 }
305
306 hits
307 }
308
309 pub fn tiles_for_fragment_in_group(
315 &self,
316 g: u32,
317 im_window: (usize, usize),
318 ) -> Vec<usize> {
319 let slices = self.program_slices_for_group(g);
320 let mut hits = Vec::new();
321
322 for (idx, s) in slices.iter().enumerate() {
323 let tile_scans = (s.scan_lo, s.scan_hi);
324
325 if ranges_overlap_u32(
326 (im_window.0 as u32, im_window.1 as u32),
327 tile_scans,
328 ) {
329 hits.push(idx);
330 }
331 }
332
333 hits
334 }
335
336 pub fn program_for_group(&self, g: u32) -> Ms2GroupProgram {
338 let mz_windows = self.group_to_isolation
339 .get(&g)
340 .map(|v| v.iter().map(|&(a,b)| (a as f32, b as f32)).collect())
341 .unwrap_or_else(Vec::new);
342
343 let scan_ranges = self.group_to_scan_ranges
344 .get(&g)
345 .cloned()
346 .unwrap_or_else(Vec::new);
347
348 let mz_union = self.group_to_mz_union
349 .get(&g)
350 .map(|&(a,b)| (a as f32, b as f32));
351
352 let scan_unions = self.group_to_scan_unions
353 .get(&g)
354 .cloned()
355 .unwrap_or_else(Vec::new);
356
357 Ms2GroupProgram { mz_windows, scan_ranges, mz_union, scan_unions }
358 }
359
360 pub fn groups_for_precursor(
361 &self,
362 prec_mz: f32,
363 im_apex: f32,
364 ) -> Vec<u32> {
365 if !prec_mz.is_finite() || !im_apex.is_finite() {
366 return Vec::new();
367 }
368
369 let mut out = Vec::new();
370 for (&g, &(mz_lo, mz_hi)) in &self.group_to_mz_union {
371 if prec_mz < mz_lo as f32 || prec_mz > mz_hi as f32 {
372 continue;
373 }
374
375 if let Some(unions) = self.group_to_scan_unions.get(&g) {
377
378 if !unions.iter().any(|&(lo, hi)| {
379 (im_apex as u32) >= lo && (im_apex as u32) <= hi
380 }) {
381 continue;
382 }
383 }
384
385 let tiles = self.tiles_for_precursor_in_group(g, prec_mz, im_apex);
386 if !tiles.is_empty() {
387 out.push(g);
388 }
389 }
390 out
391 }
392
393 pub fn tile_mz_bounds(&self, g: u32, tile_idx: usize) -> (f32, f32) {
399 let slices = self.slices_for_group(g);
400 if tile_idx >= slices.len() {
401 return (f32::NAN, f32::NAN);
402 }
403 let s = &slices[tile_idx];
404 (s.mz_lo as f32, s.mz_hi as f32)
405 }
406
407 #[inline]
408 pub fn mz_bounds_for_window_group_core(&self, g: u32) -> Option<(f32, f32)> {
409 self.group_to_mz_union.get(&g).map(|&(a,b)| (a as f32, b as f32))
410 }
411 #[inline]
412 pub fn scan_unions_for_window_group_core(&self, g: u32) -> Option<Vec<(usize, usize)>> {
413 self.group_to_scan_unions.get(&g).map(|v| v.iter().map(|&(l,r)| (l as usize, r as usize)).collect())
414 }
415
416 fn slices_for_group(&self, g: u32) -> &[ProgramSlice] {
417 self.group_to_slices.get(&g).map(|a| &a[..]).unwrap_or(&[])
418 }
419
420}
421
422pub struct TimsDatasetDIA {
423 pub loader: TimsDataLoader,
424 pub global_meta_data: GlobalMetaData,
425 pub meta_data: Vec<FrameMeta>,
426 pub dia_ms_ms_info: Vec<DiaMsMisInfo>,
427 pub dia_ms_ms_windows: Vec<DiaMsMsWindow>,
428 pub dia_index: DiaIndex,
429}
430
431impl TimsDatasetDIA {
432 pub fn new(
433 bruker_lib_path: &str,
434 data_path: &str,
435 in_memory: bool,
436 use_bruker_sdk: bool,
437 ) -> Self {
438 let global_meta_data = read_global_meta_sql(data_path).unwrap();
440 let meta_data = read_meta_data_sql(data_path).unwrap();
441 let dia_ms_mis_info = read_dia_ms_ms_info(data_path).unwrap();
442 let dia_ms_ms_windows = read_dia_ms_ms_windows(data_path).unwrap();
443
444 let scan_max_index = meta_data.iter().map(|x| x.num_scans).max().unwrap() as u32;
445 let im_lower = global_meta_data.one_over_k0_range_lower;
446 let im_upper = global_meta_data.one_over_k0_range_upper;
447
448 let tof_max_index = global_meta_data.tof_max_index;
449 let mz_lower = global_meta_data.mz_acquisition_range_lower;
450 let mz_upper = global_meta_data.mz_acquisition_range_upper;
451
452 let loader = match in_memory {
453 true => TimsDataLoader::new_in_memory(
454 bruker_lib_path,
455 data_path,
456 use_bruker_sdk,
457 scan_max_index,
458 im_lower,
459 im_upper,
460 tof_max_index,
461 mz_lower,
462 mz_upper,
463 ),
464 false => TimsDataLoader::new_lazy(
465 bruker_lib_path,
466 data_path,
467 use_bruker_sdk,
468 scan_max_index,
469 im_lower,
470 im_upper,
471 tof_max_index,
472 mz_lower,
473 mz_upper,
474 ),
475 };
476
477 let dia_index = DiaIndex::new(&meta_data, &dia_ms_mis_info, &dia_ms_ms_windows);
478
479 TimsDatasetDIA {
480 loader,
481 global_meta_data,
482 meta_data,
483 dia_ms_ms_info: dia_ms_mis_info,
484 dia_ms_ms_windows,
485 dia_index,
486 }
487 }
488
489 pub fn new_with_bruker_formula(
496 data_path: &str,
497 in_memory: bool,
498 calibration_frame_id: u32,
499 ) -> Self {
500 let meta_data = read_meta_data_sql(data_path).unwrap();
501 let global_meta_data = read_global_meta_sql(data_path).unwrap();
502 let dia_ms_mis_info = read_dia_ms_ms_info(data_path).unwrap();
503 let dia_ms_ms_windows = read_dia_ms_ms_windows(data_path).unwrap();
504
505 let loader = match in_memory {
506 true => TimsDataLoader::new_in_memory_with_bruker_formula(
507 data_path,
508 calibration_frame_id,
509 ),
510 false => {
511 TimsDataLoader::new_lazy_with_bruker_formula(data_path, calibration_frame_id)
512 }
513 };
514
515 let dia_index = DiaIndex::new(&meta_data, &dia_ms_mis_info, &dia_ms_ms_windows);
516
517 TimsDatasetDIA {
518 loader,
519 global_meta_data,
520 meta_data,
521 dia_ms_ms_info: dia_ms_mis_info,
522 dia_ms_ms_windows,
523 dia_index,
524 }
525 }
526
527 pub fn new_with_mz_calibration(
532 data_path: &str,
533 in_memory: bool,
534 tof_intercept: f64,
535 tof_slope: f64,
536 ) -> Self {
537 let meta_data = read_meta_data_sql(data_path).unwrap();
538 let global_meta_data = read_global_meta_sql(data_path).unwrap();
539 let dia_ms_mis_info = read_dia_ms_ms_info(data_path).unwrap();
540 let dia_ms_ms_windows = read_dia_ms_ms_windows(data_path).unwrap();
541
542 let scan_max_index = meta_data.iter().map(|x| x.num_scans).max().unwrap() as u32;
543 let im_lower = global_meta_data.one_over_k0_range_lower;
544 let im_upper = global_meta_data.one_over_k0_range_upper;
545
546 let loader = match in_memory {
547 true => TimsDataLoader::new_in_memory_with_mz_calibration(
548 data_path,
549 tof_intercept,
550 tof_slope,
551 im_lower,
552 im_upper,
553 scan_max_index,
554 ),
555 false => TimsDataLoader::new_lazy_with_mz_calibration(
556 data_path,
557 tof_intercept,
558 tof_slope,
559 im_lower,
560 im_upper,
561 scan_max_index,
562 ),
563 };
564
565 let dia_index = DiaIndex::new(&meta_data, &dia_ms_mis_info, &dia_ms_ms_windows);
566
567 TimsDatasetDIA {
568 loader,
569 global_meta_data,
570 meta_data,
571 dia_ms_ms_info: dia_ms_mis_info,
572 dia_ms_ms_windows,
573 dia_index,
574 }
575 }
576
577 pub fn program_for_group(&self, g: u32) -> Ms2GroupProgram {
578 self.dia_index.program_for_group(g)
579 }
580
581 pub fn program_slices_for_group(&self, group: u32) -> Vec<ProgramSlice> {
584 self.dia_ms_ms_windows
585 .iter()
586 .filter(|w| w.window_group == group)
587 .map(|w| {
588 let half = 0.5 * w.isolation_width;
589 ProgramSlice {
590 mz_lo: w.isolation_mz - half,
591 mz_hi: w.isolation_mz + half,
592 scan_lo: w.scan_num_begin,
593 scan_hi: w.scan_num_end,
594 }
595 })
596 .collect()
597 }
598
599 pub fn sample_precursor_signal(
622 &self,
623 num_frames: usize,
624 max_intensity: f64,
625 take_probability: f64,
626 ) -> TimsFrame {
627 let target_type = MsType::Precursor;
628 let pool: Vec<&FrameMeta> = self
629 .meta_data
630 .iter()
631 .filter(|x| x.ms_ms_type == 0)
632 .collect();
633 let mut rng = rand::thread_rng();
634 let mut sampled_frames: Vec<TimsFrame> =
635 self.collect_typed_noise_samples(&pool, num_frames, max_intensity,
636 take_probability, &mut rng,
637 |meta| meta.id as u32, None);
638 Self::accumulate_or_typed_empty(&mut sampled_frames, target_type)
639 }
640
641 pub fn sample_fragment_signal(
647 &self,
648 num_frames: usize,
649 window_group: u32,
650 max_intensity: f64,
651 take_probability: f64,
652 ) -> TimsFrame {
653 let target_type = MsType::FragmentDia;
654 let pool: Vec<u32> = self
655 .dia_ms_ms_info
656 .iter()
657 .filter(|x| x.window_group == window_group)
658 .map(|x| x.frame_id)
659 .collect();
660 let mut rng = rand::thread_rng();
661 let mut sampled_frames: Vec<TimsFrame> =
662 self.collect_typed_noise_samples(&pool, num_frames, max_intensity,
663 take_probability, &mut rng,
664 |&fid| fid, None);
665 Self::accumulate_or_typed_empty(&mut sampled_frames, target_type)
666 }
667
668 fn collect_typed_noise_samples<T>(
672 &self,
673 pool: &[T],
674 num_frames: usize,
675 max_intensity: f64,
676 take_probability: f64,
677 rng: &mut impl rand::Rng,
678 key_fn: impl Fn(&T) -> u32,
679 conv: Option<&(dyn IndexConverter + Sync)>,
680 ) -> Vec<TimsFrame> {
681 let mut out: Vec<TimsFrame> = Vec::with_capacity(num_frames);
682 if pool.is_empty() || num_frames == 0 {
683 return out;
684 }
685 let max_attempts = (num_frames * 8).max(16);
686 let mut attempts = 0usize;
687 use rand::seq::SliceRandom;
688 while out.len() < num_frames && attempts < max_attempts {
689 attempts += 1;
690 let Some(candidate_meta) = pool.choose(rng) else { break };
691 let frame_id = key_fn(candidate_meta);
692 let raw = 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 candidate = raw
699 .filter_ranged(0.0, 2000.0, 0, 1000, 0.0, 5.0, 1.0,
700 max_intensity, 0, i32::MAX)
701 .generate_random_sample_with_rng(take_probability, rng);
702 if candidate.scan.is_empty() || candidate.ms_type == MsType::Unknown {
706 continue;
707 }
708 out.push(candidate);
709 }
710 out
711 }
712
713 fn accumulate_or_typed_empty(
717 samples: &mut Vec<TimsFrame>,
718 target_type: MsType,
719 ) -> TimsFrame {
720 if samples.is_empty() {
721 return TimsFrame::new(
722 0, target_type, 0.0,
723 vec![], vec![], vec![], vec![], vec![],
724 );
725 }
726 let mut acc = samples.remove(0);
727 for f in samples.drain(..) {
728 acc = acc + f;
729 }
730 acc.ms_type = target_type;
734 acc
735 }
736
737 pub fn overlay_reference_noise(
750 &self,
751 frames: Vec<TimsFrame>,
752 window_groups: Vec<Option<u32>>,
753 num_precursor_frames: usize,
754 num_fragment_frames: usize,
755 max_intensity_precursor: f64,
756 max_intensity_fragment: f64,
757 take_precursor: f64,
758 take_fragment: f64,
759 seed: u64,
760 num_threads: usize,
761 ) -> Vec<TimsFrame> {
762 assert_eq!(frames.len(), window_groups.len(), "frames and window_groups must align");
763 let precursor_pool: Vec<u32> = self
764 .meta_data
765 .iter()
766 .filter(|x| x.ms_ms_type == 0)
767 .map(|m| m.id as u32)
768 .collect();
769 let mut fragment_pools: FxHashMap<u32, Vec<u32>> = FxHashMap::default();
770 for info in &self.dia_ms_ms_info {
771 fragment_pools.entry(info.window_group).or_default().push(info.frame_id);
772 }
773 let sdk_free = self.loader.sdk_free_converter();
779 let threads = if self.loader.uses_bruker_sdk() && sdk_free.is_none() {
780 1
781 } else {
782 num_threads.max(1)
783 };
784 let conv: Option<&(dyn IndexConverter + Sync)> =
785 sdk_free.as_ref().map(|c| c as &(dyn IndexConverter + Sync));
786 let pool = rayon::ThreadPoolBuilder::new()
787 .num_threads(threads)
788 .build()
789 .unwrap();
790 pool.install(|| {
791 frames
792 .into_par_iter()
793 .zip(window_groups.into_par_iter())
794 .map(|(frame, wg)| {
795 use rand::SeedableRng;
796 let stream = (frame.frame_id as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
797 let mut rng = rand::rngs::StdRng::seed_from_u64(seed ^ stream);
798 let noise = match wg {
799 Some(group) => {
800 let ids: &[u32] = fragment_pools
801 .get(&group)
802 .map(|v| v.as_slice())
803 .unwrap_or(&[]);
804 let mut s = self.collect_typed_noise_samples(
805 ids, num_fragment_frames, max_intensity_fragment,
806 take_fragment, &mut rng, |&fid| fid, conv);
807 Self::accumulate_or_typed_empty(&mut s, MsType::FragmentDia)
808 }
809 None => {
810 let mut s = self.collect_typed_noise_samples(
811 &precursor_pool, num_precursor_frames, max_intensity_precursor,
812 take_precursor, &mut rng, |&fid| fid, conv);
813 Self::accumulate_or_typed_empty(&mut s, MsType::Precursor)
814 }
815 };
816 frame + noise
817 })
818 .collect()
819 })
820 }
821
822 pub fn dia_window_groups(&self) -> Vec<u32> {
823 let mut gs: Vec<u32> = self
824 .dia_ms_ms_info
825 .iter()
826 .map(|x| x.window_group)
827 .collect();
828 gs.sort_unstable();
829 gs.dedup();
830 gs
831 }
832
833 pub fn window_groups_for_precursor(&self, prec_mz: f32, im_apex: f32) -> Vec<u32> {
834 self.dia_index.groups_for_precursor(prec_mz, im_apex)
835 }
836
837 fn frame_time_map(&self) -> FxHashMap<u32, (f32, i64)> {
839 let mut m = FxHashMap::default();
840 for fm in &self.meta_data {
841 m.insert(fm.id as u32, (fm.time as f32, fm.ms_ms_type));
842 }
843 m
844 }
845
846 pub fn fragment_frame_ids_and_times_for_group_core(&self, window_group: u32) -> (Vec<u32>, Vec<f32>) {
848 let time_map = self.frame_time_map();
849 let mut rows: Vec<(u32, f32)> = self
850 .dia_ms_ms_info
851 .iter()
852 .filter(|x| x.window_group == window_group)
853 .filter_map(|x| {
854 time_map.get(&(x.frame_id)).map(|(t, _ms2)| (x.frame_id, *t))
855 })
856 .collect();
857
858 rows.retain(|(fid, _)| time_map.get(fid).map(|(_, ty)| *ty != 0).unwrap_or(false));
860
861 rows.sort_by(|a,b| a.1.partial_cmp(&b.1).unwrap());
862 let (ids, times): (Vec<_>, Vec<_>) = rows.into_iter().unzip();
863 (ids, times)
864 }
865
866 pub fn scan_unions_for_window_group_core(&self, window_group: u32) -> Option<Vec<(usize, usize)>> {
869 let ranges: Vec<(usize, usize)> = self
870 .dia_ms_ms_windows
871 .iter()
872 .filter(|w| w.window_group == window_group)
873 .map(|w| {
874 let l = w.scan_num_begin as usize;
875 let r = w.scan_num_end as usize;
876 if l <= r { (l, r) } else { (r, l) }
877 })
878 .collect();
879 if ranges.is_empty() {
880 return None;
881 }
882 Some(merge_ranges(ranges))
883 }
884
885 pub fn mz_bounds_for_window_group_core(&self, window_group: u32) -> Option<(f32, f32)> {
887 let mut lo = f32::INFINITY;
888 let mut hi = f32::NEG_INFINITY;
889 let mut hit = false;
890 for w in &self.dia_ms_ms_windows {
891 if w.window_group == window_group {
892 let c = w.isolation_mz as f32;
893 let half = 0.5f32 * (w.isolation_width as f32);
894 lo = lo.min(c - half);
895 hi = hi.max(c + half);
896 hit = true;
897 }
898 }
899 if hit && hi > lo && lo.is_finite() && hi.is_finite() {
900 Some((lo, hi))
901 } else {
902 None
903 }
904 }
905
906 pub fn make_rt_frames_for_group(&self, window_group: u32, tof_step: i32) -> RtFrames {
909 assert!(tof_step > 0);
910
911 let (ids, times) = self.fragment_frame_ids_and_times_for_group_core(window_group);
912 assert!(
913 !ids.is_empty(),
914 "No MS2 frames for window_group {}",
915 window_group
916 );
917
918 let global_num_scans = self.meta_data
919 .iter()
920 .filter(|m| ids.binary_search(&(m.id as u32)).is_ok())
921 .map(|m| m.num_scans as usize)
922 .max()
923 .unwrap_or(0);
924
925 let scale = self.tof_scale_for_group(window_group, tof_step);
927
928 let frames: Vec<FrameBinView> = ids.par_iter()
929 .map(|&fid| build_frame_bin_view(self.get_frame(fid), &scale, global_num_scans))
930 .collect();
931
932 RtFrames {
933 frames,
934 frame_ids: ids,
935 rt_times: times,
936 scale: Arc::new(scale),
937 }
938 }
939
940 pub fn make_rt_frames_for_precursor(&self, tof_step: i32) -> RtFrames {
943 assert!(tof_step > 0);
944
945 let mut rows: Vec<(u32, f32, usize)> = self.meta_data
946 .iter()
947 .filter(|m| m.ms_ms_type == 0)
948 .map(|m| (m.id as u32, m.time as f32, m.num_scans as usize))
949 .collect();
950
951 assert!(!rows.is_empty(), "No precursor (MS1) frames found");
952 rows.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
953
954 let frame_ids: Vec<u32> = rows.iter().map(|r| r.0).collect();
955 let rt_times: Vec<f32> = rows.iter().map(|r| r.1).collect();
956 let global_num_scans = rows.iter().map(|r| r.2).max().unwrap_or(1);
957
958 let frames_for_scale: Vec<_> = frame_ids.iter().map(|&fid| self.get_frame(fid)).collect();
960 let scale = TofScale::build_from_tof(&frames_for_scale, tof_step)
961 .expect("make_rt_frames_for_precursor: failed to build TOF scale");
962
963 let frames: Vec<FrameBinView> = frame_ids.par_iter()
964 .map(|&fid| build_frame_bin_view(self.get_frame(fid), &scale, global_num_scans))
965 .collect();
966
967 RtFrames {
968 frames,
969 frame_ids,
970 rt_times,
971 scale: Arc::new(scale),
972 }
973 }
974
975 pub fn tof_scale_for_group(&self, window_group: u32, tof_step: i32) -> TofScale {
978 assert!(tof_step > 0);
979
980 let (ids, _times) = self.fragment_frame_ids_and_times_for_group_core(window_group);
981 assert!(
982 !ids.is_empty(),
983 "tof_scale_for_group: no MS2 frames for window_group {}",
984 window_group
985 );
986
987 let frames: Vec<_> = ids.iter().map(|&fid| self.get_frame(fid)).collect();
988
989 TofScale::build_from_tof(&frames, tof_step)
990 .expect("tof_scale_for_group: failed to build TOF scale (empty or degenerate)")
991 }
992
993 pub fn tof_scale_from_frames_scan(
995 &self,
996 frame_ids: &[u32],
997 tof_step: i32,
998 ) -> Option<TofScale> {
999 assert!(tof_step > 0);
1000 let frames: Vec<_> = frame_ids.iter().map(|&fid| self.get_frame(fid)).collect();
1001 TofScale::build_from_tof(&frames, tof_step)
1002 }
1003
1004 fn clusters_for_im_peaks_on_rt_frames(
1007 &self,
1008 rt: RtFrames,
1009 im_peaks: &[ImPeak1D],
1010 rt_params: RtExpandParams,
1011 build_opts: &BuildSpecOpts,
1012 eval_opts: &Eval1DOpts,
1013 require_rt_overlap: bool,
1014 num_threads: usize,
1015 ) -> Vec<ClusterResult1D> {
1016 let rt_groups = expand_many_im_peaks_along_rt(
1018 im_peaks,
1019 &rt.frames,
1020 rt.ctx(),
1021 rt.scale.as_ref(),
1022 rt_params,
1023 );
1024
1025 let specs: Vec<ClusterSpec1D> = make_specs_from_im_and_rt_groups_threads(
1027 im_peaks,
1028 &rt_groups,
1029 &rt,
1030 build_opts,
1031 require_rt_overlap,
1032 num_threads,
1033 );
1034
1035 self.evaluate_specs_1d_threads(&rt, &specs, eval_opts, num_threads)
1037 }
1038
1039 pub fn evaluate_specs_1d_threads(
1045 &self,
1046 rt_frames: &RtFrames,
1047 specs: &[ClusterSpec1D],
1048 opts: &Eval1DOpts,
1049 num_threads: usize,
1050 ) -> Vec<ClusterResult1D> {
1051 if specs.is_empty() {
1052 return Vec::new();
1053 }
1054
1055 let scale = &*rt_frames.scale;
1056
1057 let run = || {
1058 let attach_ctx = if opts.attach.attach_points {
1060 let frame_ids_local = rt_frames.frame_ids.clone();
1061 let slice = self.get_slice(frame_ids_local.clone(), num_threads.max(1));
1062 let scan_slices = slice
1063 .frames
1064 .iter()
1065 .map(|fr| build_scan_slices(fr))
1066 .collect::<Vec<_>>();
1067 let rt_axis_sec = rt_frames.rt_times.clone();
1068
1069 Some(RawAttachContext {
1070 slice,
1071 scan_slices,
1072 frame_ids_local,
1073 rt_axis_sec,
1074 })
1075 } else {
1076 None
1077 };
1078
1079 specs
1080 .par_iter()
1081 .map(|spec| {
1082 let mut res = evaluate_spec_1d(rt_frames, spec, opts);
1084
1085 decorate_with_mz_for_cluster(self, rt_frames, &mut res);(self, rt_frames, spec, scale, &mut res);
1091
1092 if let Some(ref ctx) = attach_ctx {
1094 if opts.attach.attach_points && res.raw_sum > 0.0 && res.tof_fit.area > 0.0 {
1095 let (bin_lo, bin_hi) = bin_range_for_win(scale, spec.tof_win);
1096 let (im_lo, im_hi) = (spec.im_lo, spec.im_hi);
1097 let (rt_lo, rt_hi) = (spec.rt_lo, spec.rt_hi);
1098
1099 let raw = attach_raw_points_for_spec_1d_in_ctx(
1100 ctx,
1101 scale,
1102 bin_lo,
1103 bin_hi,
1104 im_lo,
1105 im_hi,
1106 rt_lo,
1107 rt_hi,
1108 opts.attach.max_points,
1109 );
1110 res.raw_points = Some(raw);
1111 }
1112 }
1113
1114 res
1115 })
1116 .collect::<Vec<_>>()
1117 };
1118
1119 if num_threads == 0 {
1120 run()
1121 } else {
1122 ThreadPoolBuilder::new()
1123 .num_threads(num_threads)
1124 .build()
1125 .unwrap()
1126 .install(run)
1127 }
1128 }
1129
1130 pub fn clusters_for_group(
1131 &self,
1132 window_group: u32,
1133 tof_step: i32,
1134 im_peaks: &[ImPeak1D],
1135 rt_params: RtExpandParams,
1136 build_opts: &BuildSpecOpts,
1137 eval_opts: &Eval1DOpts,
1138 require_rt_overlap: bool,
1139 num_threads: usize,
1140 ) -> Vec<ClusterResult1D> {
1141 debug_assert!(
1143 im_peaks
1144 .iter()
1145 .all(|p| p.window_group == Some(window_group)),
1146 "clusters_for_group: some IM peaks have wrong or missing window_group"
1147 );
1148
1149 let rt = self.make_rt_frames_for_group(window_group, tof_step);
1151
1152 let build_opts_ms2 = build_opts.with_ms_level(2);
1154
1155 self.clusters_for_im_peaks_on_rt_frames(
1156 rt,
1157 im_peaks,
1158 rt_params,
1159 &build_opts_ms2,
1160 eval_opts,
1161 require_rt_overlap,
1162 num_threads,
1163 )
1164 }
1165
1166 pub fn clusters_for_precursor(
1167 &self,
1168 tof_step: i32,
1169 im_peaks: &[ImPeak1D],
1170 rt_params: RtExpandParams,
1171 build_opts: &BuildSpecOpts,
1172 eval_opts: &Eval1DOpts,
1173 require_rt_overlap: bool,
1174 num_threads: usize,
1175 ) -> Vec<ClusterResult1D> {
1176 debug_assert!(
1178 im_peaks.iter().all(|p| p.window_group.is_none()),
1179 "clusters_for_precursor: IM peaks unexpectedly carry a window_group"
1180 );
1181
1182 let rt = self.make_rt_frames_for_precursor(tof_step);
1184
1185 let build_opts_ms1 = build_opts.with_ms_level(1);
1187
1188 self.clusters_for_im_peaks_on_rt_frames(
1189 rt,
1190 im_peaks,
1191 rt_params,
1192 &build_opts_ms1,
1193 eval_opts,
1194 require_rt_overlap,
1195 num_threads,
1196 )
1197 }
1198
1199 pub fn build_pseudo_spectra_from_clusters_geom(
1203 &self,
1204 ms1: &[ClusterResult1D],
1205 ms2: &[ClusterResult1D],
1206 features: Option<&[SimpleFeature]>,
1207 cand_opts: &CandidateOpts,
1208 score_opts: &ScoreOpts,
1209 pseudo_opts: &PseudoSpecOpts,
1210 ) -> PseudoBuildResult {
1211 build_pseudo_spectra_end_to_end(
1212 self,
1213 ms1,
1214 ms2,
1215 features,
1216 cand_opts,
1217 score_opts,
1218 pseudo_opts,
1219 )
1220 }
1221
1222 pub fn build_pseudo_spectra_from_clusters_xic(
1226 &self,
1227 ms1: &[ClusterResult1D],
1228 ms2: &[ClusterResult1D],
1229 features: Option<&[SimpleFeature]>,
1230 cand_opts: &CandidateOpts,
1231 xic_opts: &XicScoreOpts,
1232 pseudo_opts: &PseudoSpecOpts,
1233 ) -> PseudoBuildResult {
1234 build_pseudo_spectra_end_to_end_xic(
1235 self,
1236 ms1,
1237 ms2,
1238 features,
1239 cand_opts,
1240 xic_opts,
1241 pseudo_opts,
1242 )
1243 }
1244
1245 pub fn build_pseudo_spectra_all_pairs_from_clusters(
1247 &self,
1248 ms1: &[ClusterResult1D],
1249 ms2: &[ClusterResult1D],
1250 features: Option<&[SimpleFeature]>,
1251 pseudo_opts: &PseudoSpecOpts,
1252 ) -> PseudoBuildResult {
1253 build_pseudo_spectra_all_pairs(self, ms1, ms2, features, pseudo_opts)
1254 }
1255
1256 pub fn clusters_for_group_opts(
1262 &self,
1263 window_group: u32,
1264 im_peaks: &[ImPeak1D],
1265 opts: &ClusterExtractionOpts,
1266 ) -> Vec<ClusterResult1D> {
1267 self.clusters_for_group(
1268 window_group,
1269 opts.tof_step,
1270 im_peaks,
1271 opts.rt_params.clone(),
1272 &opts.build_opts,
1273 &opts.eval_opts,
1274 opts.require_rt_overlap,
1275 opts.num_threads,
1276 )
1277 }
1278
1279 pub fn clusters_for_precursor_opts(
1281 &self,
1282 im_peaks: &[ImPeak1D],
1283 opts: &ClusterExtractionOpts,
1284 ) -> Vec<ClusterResult1D> {
1285 self.clusters_for_precursor(
1286 opts.tof_step,
1287 im_peaks,
1288 opts.rt_params.clone(),
1289 &opts.build_opts,
1290 &opts.eval_opts,
1291 opts.require_rt_overlap,
1292 opts.num_threads,
1293 )
1294 }
1295
1296 pub fn tof_rt_grid_precursor(&self, tof_step: i32) -> TofRtGrid {
1299 let rt = self.make_rt_frames_for_precursor(tof_step);
1300 build_tof_rt_grid_full(&rt, None)
1301 }
1302
1303 pub fn tof_rt_grid_for_group(&self, window_group: u32, tof_step: i32) -> TofRtGrid {
1306 let rt = self.make_rt_frames_for_group(window_group, tof_step);
1307 build_tof_rt_grid_full(&rt, Some(window_group))
1308 }
1309
1310 pub fn debug_extract_raw_for_clusters(
1323 &self,
1324 clusters: &[ClusterResult1D],
1325 window_group: Option<u32>,
1326 tof_step: i32,
1327 max_points: Option<usize>,
1328 num_threads: usize,
1329 ) -> Vec<RawPoints> {
1330 if clusters.is_empty() {
1331 return Vec::new();
1332 }
1333
1334 let ms_level = clusters[0].ms_level;
1336 debug_assert!(
1337 clusters.iter().all(|c| c.ms_level == ms_level),
1338 "debug_extract_raw_for_clusters: mixed ms_level in cluster list"
1339 );
1340
1341 let clusters: Vec<ClusterResult1D> = clusters
1343 .iter()
1344 .filter(|c| match window_group {
1345 Some(g) => c.window_group == Some(g),
1346 None => true,
1347 })
1348 .cloned()
1349 .collect();
1350
1351 if clusters.is_empty() {
1352 return Vec::new();
1353 }
1354
1355 let rt_frames = if ms_level == 1 {
1360 self.make_rt_frames_for_precursor(tof_step)
1362 } else {
1363 let g = match window_group {
1365 Some(g) => g,
1366 None => clusters[0]
1367 .window_group
1368 .expect("MS2 cluster without window_group; pass window_group explicitly"),
1369 };
1370 self.make_rt_frames_for_group(g, tof_step)
1371 };
1372
1373 clusters
1375 .iter()
1376 .map(|c| {
1377 let (rt_lo, rt_hi) = c.rt_window;
1378 let (im_lo, im_hi) = c.im_window;
1379 let (tof_lo, tof_hi) = c.tof_window;
1380
1381 attach_raw_points_for_spec_1d_threads(
1382 self,
1383 &rt_frames,
1384 tof_lo,
1385 tof_hi,
1386 im_lo,
1387 im_hi,
1388 rt_lo,
1389 rt_hi,
1390 max_points,
1391 num_threads,
1392 )
1393 })
1394 .collect()
1395 }
1396}
1397
1398impl TimsData for TimsDatasetDIA {
1399 fn get_frame(&self, frame_id: u32) -> TimsFrame {
1400 self.loader.get_frame(frame_id)
1401 }
1402
1403 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
1404 self.loader.get_raw_frame(frame_id)
1405 }
1406
1407 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1408 self.loader.get_slice(frame_ids, num_threads)
1409 }
1410 fn get_acquisition_mode(&self) -> AcquisitionMode {
1411 self.loader.get_acquisition_mode().clone()
1412 }
1413
1414 fn get_frame_count(&self) -> i32 {
1415 self.loader.get_frame_count()
1416 }
1417
1418 fn get_data_path(&self) -> &str {
1419 &self.loader.get_data_path()
1420 }
1421}
1422
1423impl IndexConverter for TimsDatasetDIA {
1424 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1425 self.loader
1426 .get_index_converter()
1427 .tof_to_mz(frame_id, tof_values)
1428 }
1429
1430 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1431 self.loader
1432 .get_index_converter()
1433 .mz_to_tof(frame_id, mz_values)
1434 }
1435
1436 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1437 self.loader
1438 .get_index_converter()
1439 .scan_to_inverse_mobility(frame_id, scan_values)
1440 }
1441
1442 fn inverse_mobility_to_scan(
1443 &self,
1444 frame_id: u32,
1445 inverse_mobility_values: &Vec<f64>,
1446 ) -> Vec<u32> {
1447 self.loader
1448 .get_index_converter()
1449 .inverse_mobility_to_scan(frame_id, inverse_mobility_values)
1450 }
1451}
1452
1453pub fn attach_raw_points_for_spec_1d_threads(
1454 ds: &TimsDatasetDIA,
1455 rt_frames: &RtFrames,
1456 final_bin_lo: usize,
1457 final_bin_hi: usize,
1458 final_im_lo: usize,
1459 final_im_hi: usize,
1460 final_rt_lo: usize,
1461 final_rt_hi: usize,
1462 max_points: Option<usize>,
1463 num_threads: usize,
1464) -> RawPoints {
1465 let scale = &*rt_frames.scale;
1466
1467 let n_bins = scale.num_bins();
1469 if n_bins == 0 {
1470 return RawPoints::default();
1471 }
1472
1473 let mut bin_lo = final_bin_lo.min(n_bins.saturating_sub(1));
1474 let mut bin_hi = final_bin_hi.min(n_bins.saturating_sub(1));
1475 if bin_lo > bin_hi {
1476 std::mem::swap(&mut bin_lo, &mut bin_hi);
1477 }
1478
1479 let mut axis_lo = scale.edges[bin_lo];
1482 let hi_edge_idx = (bin_hi + 1).min(scale.edges.len().saturating_sub(1));
1483 let mut axis_hi = cushion_hi_edge(scale, scale.edges[hi_edge_idx]);
1484
1485 let frame_ids_local = rt_frames.frame_ids[final_rt_lo..=final_rt_hi].to_vec();
1487 let slice = ds.get_slice(frame_ids_local.clone(), num_threads.max(1));
1488 let scan_slices: Vec<Vec<ScanSlice>> =
1489 slice.frames.iter().map(|fr| build_scan_slices(fr)).collect();
1490
1491 let mut total = 0usize;
1493 for (fi, fr) in slice.frames.iter().enumerate() {
1494 let tofs = &fr.tof; for sl in &scan_slices[fi] {
1496 if sl.scan < final_im_lo || sl.scan > final_im_hi {
1497 continue;
1498 }
1499 let l = lower_bound_tof(tofs, sl.start, sl.end, axis_lo);
1500 let r = upper_bound_tof(tofs, sl.start, sl.end, axis_hi);
1501 total += r.saturating_sub(l);
1502 }
1503 }
1504
1505 if total == 0 {
1507 let lo_idx = bin_lo.saturating_sub(1);
1508 let hi_edge_idx_wide =
1509 (bin_hi + 2).min(n_bins).min(scale.edges.len().saturating_sub(1));
1510
1511 let try_lo = scale.edges[lo_idx];
1512 let try_hi = cushion_hi_edge(scale, scale.edges[hi_edge_idx_wide]);
1513
1514 let mut total2 = 0usize;
1515 for (fi, fr) in slice.frames.iter().enumerate() {
1516 let tofs = &fr.tof;
1517 for sl in &scan_slices[fi] {
1518 if sl.scan < final_im_lo || sl.scan > final_im_hi {
1519 continue;
1520 }
1521 let l = lower_bound_tof(tofs, sl.start, sl.end, try_lo);
1522 let r = upper_bound_tof(tofs, sl.start, sl.end, try_hi);
1523 total2 += r.saturating_sub(l);
1524 }
1525 }
1526
1527 if total2 > 0 {
1528 total = total2;
1529 axis_lo = try_lo;
1530 axis_hi = try_hi;
1531 }
1532 }
1533
1534 if total == 0 {
1536 return RawPoints::default();
1537 }
1538
1539 let stride = max_points.map(|cap| thin_stride(total, cap)).unwrap_or(1);
1540 let reserve = total / stride + 8;
1541
1542 let mut pts = RawPoints {
1543 mz: Vec::with_capacity(reserve),
1544 rt: Vec::with_capacity(reserve),
1545 im: Vec::with_capacity(reserve),
1546 scan: Vec::with_capacity(reserve),
1547 intensity: Vec::with_capacity(reserve),
1548 tof: Vec::with_capacity(reserve),
1549 frame: Vec::with_capacity(reserve),
1550 };
1551
1552 let rt_axis_sec = rt_frames.rt_times[final_rt_lo..=final_rt_hi].to_vec();
1553
1554 let mut seen = 0usize;
1556 for (fi, fr) in slice.frames.iter().enumerate() {
1557 let mz = &fr.ims_frame.mz;
1558 let it = &fr.ims_frame.intensity;
1559 let ims = &fr.ims_frame.mobility;
1560 let tofs = &fr.tof;
1561
1562 let len_all = mz.len().min(it.len()).min(ims.len()).min(tofs.len());
1563 let rt_val = rt_axis_sec[fi];
1564 let frame_id = frame_ids_local[fi];
1565
1566 for sl in &scan_slices[fi] {
1567 let s_abs = sl.scan;
1568 if s_abs < final_im_lo || s_abs > final_im_hi {
1569 continue;
1570 }
1571
1572 let mut l = lower_bound_tof(tofs, sl.start, sl.end, axis_lo);
1573 let mut r = upper_bound_tof(tofs, sl.start, sl.end, axis_hi);
1574 if r > len_all {
1575 r = len_all;
1576 }
1577 if l >= r {
1578 continue;
1579 }
1580
1581 while l < r {
1582 if stride == 1 || (seen % stride == 0) {
1583 pts.mz.push(mz[l] as f32);
1585 pts.rt.push(rt_val);
1586 pts.im.push(ims[l] as f32);
1587 pts.scan.push(s_abs as u32);
1588 pts.intensity.push(it[l] as f32);
1589 pts.frame.push(frame_id);
1590 pts.tof.push(tofs[l]);
1591 }
1592 seen += 1;
1593 l += 1;
1594 }
1595 }
1596 }
1597
1598 pts
1599}
1600
1601#[inline]
1603fn lower_bound_tof(tofs: &[i32], start: usize, end: usize, x: f32) -> usize {
1604 let mut lo = start;
1605 let mut hi = end;
1606 let xf = x as f64;
1607 while lo < hi {
1608 let mid = (lo + hi) >> 1;
1609 if (tofs[mid] as f64) < xf {
1610 lo = mid + 1;
1611 } else {
1612 hi = mid;
1613 }
1614 }
1615 lo
1616}
1617
1618#[inline]
1620fn upper_bound_tof(tofs: &[i32], start: usize, end: usize, x: f32) -> usize {
1621 let mut lo = start;
1622 let mut hi = end;
1623 let xf = x as f64;
1624 while lo < hi {
1625 let mid = (lo + hi) >> 1;
1626 if (tofs[mid] as f64) <= xf {
1627 lo = mid + 1;
1628 } else {
1629 hi = mid;
1630 }
1631 }
1632 lo
1633}
1634
1635#[inline]
1647fn cushion_hi_edge(scale: &TofScale, hi_edge: f32) -> f32 {
1648 let edges = &scale.edges;
1649 if edges.len() >= 2 {
1650 let bw = (edges[1] - edges[0]).abs().max(1e-6);
1652 hi_edge + 0.01 * bw
1653 } else {
1654 hi_edge
1655 }
1656}
1657
1658#[inline]
1659fn thin_stride(total: usize, cap: usize) -> usize {
1660 if cap == 0 || total <= cap {
1661 1
1662 } else {
1663 (total + cap - 1) / cap
1664 }
1665}
1666
1667
1668#[cfg(test)]
1669mod tests {
1670 use super::*;
1671 use mscore::data::spectrum::MsType;
1672 use mscore::timstof::frame::TimsFrame;
1673
1674 fn make_frame(frame_id: i32, ms_type: MsType) -> TimsFrame {
1675 TimsFrame::new(
1678 frame_id,
1679 ms_type,
1680 0.0,
1681 vec![10],
1682 vec![0.8],
1683 vec![500],
1684 vec![500.0],
1685 vec![10.0],
1686 )
1687 }
1688
1689 #[test]
1690 fn accumulate_or_typed_empty_returns_typed_empty_when_pool_is_empty() {
1691 let mut samples: Vec<TimsFrame> = vec![];
1696 let noise = TimsDatasetDIA::accumulate_or_typed_empty(
1697 &mut samples, MsType::Precursor,
1698 );
1699 assert_eq!(noise.ms_type, MsType::Precursor,
1700 "typed-empty must carry the requested MsType, not Unknown");
1701 assert!(noise.scan.is_empty());
1702 assert!(noise.ims_frame.mz.is_empty());
1703 }
1704
1705 #[test]
1706 fn accumulate_or_typed_empty_stamps_type_even_after_inner_unknown() {
1707 let mut samples = vec![
1713 make_frame(1, MsType::Unknown),
1714 make_frame(2, MsType::FragmentDia),
1715 ];
1716 let noise = TimsDatasetDIA::accumulate_or_typed_empty(
1717 &mut samples, MsType::FragmentDia,
1718 );
1719 assert_eq!(noise.ms_type, MsType::FragmentDia);
1720 }
1721
1722 #[test]
1723 fn accumulate_preserves_type_on_clean_input() {
1724 let mut samples = vec![
1725 make_frame(1, MsType::FragmentDia),
1726 make_frame(2, MsType::FragmentDia),
1727 make_frame(3, MsType::FragmentDia),
1728 ];
1729 let noise = TimsDatasetDIA::accumulate_or_typed_empty(
1730 &mut samples, MsType::FragmentDia,
1731 );
1732 assert_eq!(noise.ms_type, MsType::FragmentDia);
1733 assert!(!noise.scan.is_empty());
1735 }
1736
1737 #[test]
1738 fn typed_empty_frame_uses_correct_default_frame_id() {
1739 let mut samples: Vec<TimsFrame> = vec![];
1745 let noise = TimsDatasetDIA::accumulate_or_typed_empty(
1746 &mut samples, MsType::Precursor,
1747 );
1748 assert_eq!(noise.frame_id, 0);
1749 assert_eq!(noise.ims_frame.retention_time, 0.0);
1750 }
1751}