1use std::collections::{BTreeMap, HashMap};
2use std::fmt::Display;
3use itertools::{izip, multizip};
4use rand::distributions::{Uniform, Distribution};
5use rand::rngs::ThreadRng;
6use statrs::distribution::Normal;
7use crate::data::spectrum::{MsType, ToResolution, Vectorized};
8use crate::data::peptide::FragmentType;
9
10#[derive(Clone, Debug)]
11pub struct PeakAnnotation {
12 pub contributions: Vec<ContributionSource>,
13}
14
15impl PeakAnnotation {
16 pub fn new_random_noise(intensity: f64) -> Self {
17 let contribution_source = ContributionSource {
18 intensity_contribution: intensity,
19 source_type: SourceType::RandomNoise,
20 signal_attributes: None,
21 };
22
23 PeakAnnotation {
24 contributions: vec![contribution_source],
25 }
26 }
27}
28
29
30#[derive(Clone, Debug)]
31pub struct ContributionSource {
32 pub intensity_contribution: f64,
33 pub source_type: SourceType,
34 pub signal_attributes: Option<SignalAttributes>,
35}
36
37#[derive(Clone, Debug, PartialEq)]
38pub enum SourceType {
39 Signal,
40 ChemicalNoise,
41 RandomNoise,
42 Unknown,
43}
44
45impl SourceType {
46 pub fn new(source_type: i32) -> Self {
47 match source_type {
48 0 => SourceType::Signal,
49 1 => SourceType::ChemicalNoise,
50 2 => SourceType::RandomNoise,
51 3 => SourceType::Unknown,
52 _ => panic!("Invalid source type"),
53 }
54 }
55}
56
57impl Display for SourceType {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 SourceType::Signal => write!(f, "Signal"),
61 SourceType::ChemicalNoise => write!(f, "ChemicalNoise"),
62 SourceType::RandomNoise => write!(f, "RandomNoise"),
63 SourceType::Unknown => write!(f, "Unknown"),
64 }
65 }
66}
67
68#[derive(Clone, Debug)]
69pub struct SignalAttributes {
70 pub charge_state: i32,
71 pub peptide_id: i32,
72 pub isotope_peak: i32,
73 pub fragment_kind: Option<FragmentType>,
78 pub fragment_ordinal: Option<i32>,
80}
81
82impl SignalAttributes {
83 pub fn description(&self) -> Option<String> {
88 match (self.fragment_kind, self.fragment_ordinal) {
89 (Some(kind), Some(ordinal)) => {
90 Some(format!("{}_{}_{}", kind, ordinal, self.isotope_peak))
91 }
92 _ => None,
93 }
94 }
95}
96
97#[derive(Clone, Debug)]
98pub struct MzSpectrumAnnotated {
99 pub mz: Vec<f64>,
100 pub intensity: Vec<f64>,
101 pub annotations: Vec<PeakAnnotation>,
102}
103
104impl MzSpectrumAnnotated {
105 pub fn new(mz: Vec<f64>, intensity: Vec<f64>, annotations: Vec<PeakAnnotation>) -> Self {
106 assert!(mz.len() == intensity.len() && intensity.len() == annotations.len());
107 let mut triples: Vec<(f64, f64, PeakAnnotation)> = izip!(mz, intensity, annotations).collect();
113 triples.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
114
115 let mut mz = Vec::with_capacity(triples.len());
116 let mut intensity = Vec::with_capacity(triples.len());
117 let mut annotations = Vec::with_capacity(triples.len());
118 for (m, i, a) in triples {
119 mz.push(m);
120 intensity.push(i);
121 annotations.push(a);
122 }
123
124 MzSpectrumAnnotated { mz, intensity, annotations }
125 }
126
127 pub fn filter_ranged(&self, mz_min: f64, mz_max: f64, intensity_min: f64, intensity_max: f64) -> Self {
128 let mut mz_filtered: Vec<f64> = Vec::new();
129 let mut intensity_filtered: Vec<f64> = Vec::new();
130 let mut annotations_filtered: Vec<PeakAnnotation> = Vec::new();
131
132 for (mz, intensity, annotation) in izip!(self.mz.iter(), self.intensity.iter(), self.annotations.iter()) {
133 if *mz >= mz_min && *mz <= mz_max && *intensity >= intensity_min && *intensity <= intensity_max {
134 mz_filtered.push(*mz);
135 intensity_filtered.push(*intensity);
136 annotations_filtered.push(annotation.clone());
137 }
138 }
139 assert!(mz_filtered.len() == intensity_filtered.len() && intensity_filtered.len() == annotations_filtered.len());
141
142 MzSpectrumAnnotated {
143 mz: mz_filtered,
144 intensity: intensity_filtered,
145 annotations: annotations_filtered,
146 }
147 }
148
149 pub fn add_mz_noise_uniform(&self, ppm: f64, right_drag: bool) -> Self {
150 let mut rng = rand::thread_rng();
151 self.add_mz_noise(ppm, &mut rng, |rng, mz, ppm| {
152
153 let ppm_mz = match right_drag {
154 true => mz * ppm / 1e6 / 2.0,
155 false => mz * ppm / 1e6,
156 };
157
158 let dist = match right_drag {
159 true => Uniform::from(mz - (ppm_mz / 3.0)..=mz + ppm_mz),
160 false => Uniform::from(mz - ppm_mz..=mz + ppm_mz),
161 };
162
163 dist.sample(rng)
164 })
165 }
166
167 pub fn add_mz_noise_normal(&self, ppm: f64) -> Self {
168 let mut rng = rand::thread_rng();
169 self.add_mz_noise(ppm, &mut rng, |rng, mz, ppm| {
170 let ppm_mz = mz * ppm / 1e6;
171 let dist = Normal::new(mz, ppm_mz / 3.0).unwrap(); dist.sample(rng)
173 })
174 }
175
176 fn add_mz_noise<F>(&self, ppm: f64, rng: &mut ThreadRng, noise_fn: F) -> Self
177 where
178 F: Fn(&mut ThreadRng, f64, f64) -> f64,
179 {
180 let mz: Vec<f64> = self.mz.iter().map(|&mz_value| noise_fn(rng, mz_value, ppm)).collect();
181 let spectrum = MzSpectrumAnnotated { mz, intensity: self.intensity.clone(), annotations: self.annotations.clone()};
182
183 spectrum.to_resolution(6)
185 }
186
187 pub fn to_windows(&self, window_length: f64, overlapping: bool, min_peaks: usize, min_intensity: f64) -> BTreeMap<i32, MzSpectrumAnnotated> {
188 let mut splits = BTreeMap::new();
189
190 for (i, &mz) in self.mz.iter().enumerate() {
191 let intensity = self.intensity[i];
192 let annotation = self.annotations[i].clone();
193
194 let tmp_key = (mz / window_length).floor() as i32;
195
196 splits.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).mz.push(mz);
197 splits.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).intensity.push(intensity);
198 splits.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).annotations.push(annotation);
199 }
200
201 if overlapping {
202 let mut splits_offset = BTreeMap::new();
203
204 for (i, &mmz) in self.mz.iter().enumerate() {
205 let intensity = self.intensity[i];
206 let annotation = self.annotations[i].clone();
207
208 let tmp_key = -((mmz + window_length / 2.0) / window_length).floor() as i32;
209
210 splits_offset.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).mz.push(mmz);
211 splits_offset.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).intensity.push(intensity);
212 splits_offset.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).annotations.push(annotation);
213 }
214
215 for (key, val) in splits_offset {
216 splits.entry(key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).mz.extend(val.mz);
217 splits.entry(key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).intensity.extend(val.intensity);
218 splits.entry(key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).annotations.extend(val.annotations);
219 }
220 }
221
222 splits.retain(|_, spectrum| {
223 spectrum.mz.len() >= min_peaks && spectrum.intensity.iter().cloned().max_by(
224 |a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap_or(0.0) >= min_intensity
225 });
226
227 splits
228 }
229}
230
231impl std::ops::Add for MzSpectrumAnnotated {
232 type Output = Self;
233 fn add(self, other: Self) -> Self {
234
235 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
236 let mut spec_map: BTreeMap<i64, (f64, PeakAnnotation)> = BTreeMap::new();
237
238 for ((mz, intensity), annotation) in self.mz.iter().zip(self.intensity.iter()).zip(self.annotations.iter()) {
239 let key = quantize(*mz);
240 spec_map.insert(key, (*intensity, annotation.clone()));
241 }
242
243 for ((mz, intensity), annotation) in other.mz.iter().zip(other.intensity.iter()).zip(other.annotations.iter()) {
244 let key = quantize(*mz);
245 spec_map.entry(key).and_modify(|e| {
246 e.0 += *intensity;
247 e.1.contributions.extend(annotation.contributions.clone());
248 }).or_insert((*intensity, annotation.clone()));
249 }
250
251 let mz: Vec<f64> = spec_map.keys().map(|&key| key as f64 / 1_000_000.0).collect();
252 let intensity: Vec<f64> = spec_map.values().map(|(intensity, _)| *intensity).collect();
253 let annotations: Vec<PeakAnnotation> = spec_map.values().map(|(_, annotation)| annotation.clone()).collect();
254
255 assert!(mz.len() == intensity.len() && intensity.len() == annotations.len());
256
257 MzSpectrumAnnotated {
258 mz,
259 intensity,
260 annotations,
261 }
262 }
263}
264
265impl ToResolution for MzSpectrumAnnotated {
266 fn to_resolution(&self, resolution: i32) -> Self {
267 let mut spec_map: BTreeMap<i64, (f64, PeakAnnotation)> = BTreeMap::new();
268 let quantize = |mz: f64| -> i64 { (mz * 10.0_f64.powi(resolution)).round() as i64 };
269
270 for ((mz, intensity), annotation) in self.mz.iter().zip(self.intensity.iter()).zip(self.annotations.iter()) {
271 let key = quantize(*mz);
272 spec_map.entry(key).and_modify(|e| {
273 e.0 += *intensity;
274 e.1.contributions.extend(annotation.contributions.clone());
275 }).or_insert((*intensity, annotation.clone()));
276 }
277
278 let mz: Vec<f64> = spec_map.keys().map(|&key| key as f64 / 10.0_f64.powi(resolution)).collect();
279 let intensity: Vec<f64> = spec_map.values().map(|(intensity, _)| *intensity).collect();
280 let annotations: Vec<PeakAnnotation> = spec_map.values().map(|(_, annotation)| annotation.clone()).collect();
281
282 assert!(mz.len() == intensity.len() && intensity.len() == annotations.len());
283
284 MzSpectrumAnnotated {
285 mz,
286 intensity,
287 annotations,
288 }
289 }
290}
291
292impl std::ops::Mul<f64> for MzSpectrumAnnotated {
293 type Output = Self;
294 fn mul(self, scale: f64) -> Self::Output{
295
296 let mut scaled_intensities: Vec<f64> = vec![0.0; self.intensity.len()];
297
298 for (idx,intensity) in self.intensity.iter().enumerate(){
299 scaled_intensities[idx] = scale*intensity;
300 }
301
302 let mut scaled_annotations: Vec<PeakAnnotation> = Vec::new();
303
304 for annotation in self.annotations.iter(){
305 let mut scaled_contributions: Vec<ContributionSource> = Vec::new();
306 for contribution in annotation.contributions.iter(){
307 let scaled_intensity = (contribution.intensity_contribution*scale).round();
308 let scaled_contribution = ContributionSource{
309 intensity_contribution: scaled_intensity,
310 source_type: contribution.source_type.clone(),
311 signal_attributes: contribution.signal_attributes.clone(),
312 };
313 scaled_contributions.push(scaled_contribution);
314 }
315 let scaled_annotation = PeakAnnotation{
316 contributions: scaled_contributions,
317 };
318 scaled_annotations.push(scaled_annotation);
319 }
320
321 MzSpectrumAnnotated { mz: self.mz.clone(), intensity: scaled_intensities, annotations: scaled_annotations }
322 }
323}
324
325impl Vectorized<MzSpectrumAnnotatedVectorized> for MzSpectrumAnnotated {
326 fn vectorized(&self, resolution: i32) -> MzSpectrumAnnotatedVectorized {
327
328 let quantize = |mz: f64| -> i64 { (mz * 10.0_f64.powi(resolution)).round() as i64 };
329
330 let binned_spec = self.to_resolution(resolution);
331 let mut indices: Vec<u32> = Vec::with_capacity(binned_spec.mz.len());
332 let mut values: Vec<f64> = Vec::with_capacity(binned_spec.mz.len());
333 let mut annotations: Vec<PeakAnnotation> = Vec::with_capacity(binned_spec.mz.len());
334
335 for (mz, intensity, annotation) in izip!(binned_spec.mz.iter(), binned_spec.intensity.iter(), binned_spec.annotations.iter()) {
336 indices.push(quantize(*mz) as u32);
337 values.push(*intensity);
338 annotations.push(annotation.clone());
339 }
340
341 MzSpectrumAnnotatedVectorized {
342 indices,
343 values,
344 annotations,
345 }
346 }
347}
348
349#[derive(Clone, Debug)]
350pub struct MzSpectrumAnnotatedVectorized {
351 pub indices: Vec<u32>,
352 pub values: Vec<f64>,
353 pub annotations: Vec<PeakAnnotation>,
354}
355
356#[derive(Clone, Debug)]
357pub struct TimsSpectrumAnnotated {
358 pub frame_id: i32,
359 pub scan: u32,
360 pub retention_time: f64,
361 pub mobility: f64,
362 pub ms_type: MsType,
363 pub tof: Vec<u32>,
364 pub spectrum: MzSpectrumAnnotated,
365}
366
367impl TimsSpectrumAnnotated {
368 pub fn new(frame_id: i32, scan: u32, retention_time: f64, mobility: f64, ms_type: MsType, tof: Vec<u32>, spectrum: MzSpectrumAnnotated) -> Self {
369 assert!(tof.len() == spectrum.mz.len() && spectrum.mz.len() == spectrum.intensity.len() && spectrum.intensity.len() == spectrum.annotations.len());
370 let mut mz_intensity_annotations: Vec<(u32, f64, f64, PeakAnnotation)> = izip!(tof.iter(), spectrum.mz.iter(), spectrum.intensity.iter(),
372 spectrum.annotations.iter()).map(|(tof, mz, intensity, annotation)| (*tof, *mz, *intensity, annotation.clone())).collect();
373 mz_intensity_annotations.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
374 TimsSpectrumAnnotated {
375 frame_id,
376 scan,
377 retention_time,
378 mobility,
379 ms_type,
380 tof: mz_intensity_annotations.iter().map(|(tof, _, _, _)| *tof).collect(),
381 spectrum: MzSpectrumAnnotated {
382 mz: mz_intensity_annotations.iter().map(|(_, mz, _, _)| *mz).collect(),
383 intensity: mz_intensity_annotations.iter().map(|(_, _, intensity, _)| *intensity).collect(),
384 annotations: mz_intensity_annotations.iter().map(|(_, _, _, annotation)| annotation.clone()).collect(),
385 },
386 }
387 }
388
389 pub fn filter_ranged(&self, mz_min: f64, mz_max: f64, intensity_min: f64, intensity_max: f64) -> Self {
390 let mut tof_filtered: Vec<u32> = Vec::new();
391 let mut mz_filtered: Vec<f64> = Vec::new();
392 let mut intensity_filtered: Vec<f64> = Vec::new();
393 let mut annotations_filtered: Vec<PeakAnnotation> = Vec::new();
394
395 for (tof, mz, intensity, annotation) in izip!(self.tof.iter(), self.spectrum.mz.iter(), self.spectrum.intensity.iter(), self.spectrum.annotations.iter()) {
396 if *mz >= mz_min && *mz <= mz_max && *intensity >= intensity_min && *intensity <= intensity_max {
397 tof_filtered.push(*tof);
398 mz_filtered.push(*mz);
399 intensity_filtered.push(*intensity);
400 annotations_filtered.push(annotation.clone());
401 }
402 }
403
404 assert!(tof_filtered.len() == mz_filtered.len() && mz_filtered.len() == intensity_filtered.len() && intensity_filtered.len() == annotations_filtered.len());
405
406 TimsSpectrumAnnotated {
407 frame_id: self.frame_id,
408 scan: self.scan,
409 retention_time: self.retention_time,
410 mobility: self.mobility,
411 ms_type: self.ms_type.clone(),
412 tof: tof_filtered,
413 spectrum: MzSpectrumAnnotated::new(mz_filtered, intensity_filtered, annotations_filtered),
414 }
415 }
416
417 pub fn add_mz_noise_uniform(&self, ppm: f64, right_drag: bool) -> Self {
418 TimsSpectrumAnnotated {
419 frame_id: self.frame_id,
420 scan: self.scan,
421 retention_time: self.retention_time,
422 mobility: self.mobility,
423 ms_type: self.ms_type.clone(),
424 tof: self.tof.clone(),
426 spectrum: self.spectrum.add_mz_noise_uniform(ppm, right_drag),
427 }
428 }
429
430 pub fn add_mz_noise_normal(&self, ppm: f64) -> Self {
431 TimsSpectrumAnnotated {
432 frame_id: self.frame_id,
433 scan: self.scan,
434 retention_time: self.retention_time,
435 mobility: self.mobility,
436 ms_type: self.ms_type.clone(),
437 tof: self.tof.clone(),
439 spectrum: self.spectrum.add_mz_noise_normal(ppm),
440 }
441 }
442
443 pub fn to_windows(
444 &self,
445 window_length: f64,
446 overlapping: bool,
447 min_peaks: usize,
448 min_intensity: f64,
449 ) -> BTreeMap<i32, TimsSpectrumAnnotated> {
450 let mut buckets: BTreeMap<i32, Vec<(u32, f64, f64, PeakAnnotation)>> = BTreeMap::new();
452 for (&tof, &mz, &intensity, annotation) in
453 izip!(
454 &self.tof,
455 &self.spectrum.mz,
456 &self.spectrum.intensity,
457 &self.spectrum.annotations
458 )
459 {
460 let idx = (mz / window_length).floor() as i32;
461 buckets.entry(idx)
462 .or_default()
463 .push((tof, mz, intensity, annotation.clone()));
464 }
465
466 if overlapping {
468 let mut off: BTreeMap<i32, Vec<(u32, f64, f64, PeakAnnotation)>> = BTreeMap::new();
469 let half = window_length / 2.0;
470 for (&tof, &mz, &intensity, annotation) in
471 izip!(
472 &self.tof,
473 &self.spectrum.mz,
474 &self.spectrum.intensity,
475 &self.spectrum.annotations
476 )
477 {
478 let idx = -(((mz + half) / window_length).floor() as i32);
479 off.entry(idx)
480 .or_default()
481 .push((tof, mz, intensity, annotation.clone()));
482 }
483 for (k, v) in off {
484 buckets.entry(k).or_default().extend(v);
485 }
486 }
487
488 let mut out = BTreeMap::new();
490 for (idx, group) in buckets {
491 if group.len() < min_peaks {
492 continue;
493 }
494 let max_i = group.iter().map(|&(_, _, i, _)| i).fold(0.0, f64::max);
495 if max_i < min_intensity {
496 continue;
497 }
498
499 let mut tofs = Vec::with_capacity(group.len());
501 let mut mzs = Vec::with_capacity(group.len());
502 let mut ints = Vec::with_capacity(group.len());
503 let mut annots = Vec::with_capacity(group.len());
504 for (tof, mz, intensity, annotation) in group {
505 tofs.push(tof);
506 mzs.push(mz);
507 ints.push(intensity);
508 annots.push(annotation);
509 }
510
511 let window_spec = MzSpectrumAnnotated::new(mzs, ints, annots);
513
514 let sub = TimsSpectrumAnnotated {
515 frame_id: self.frame_id,
516 scan: self.scan,
517 retention_time: self.retention_time,
518 mobility: self.mobility,
519 ms_type: self.ms_type.clone(),
520 tof: tofs,
521 spectrum: window_spec,
522 };
523
524 out.insert(idx, sub);
525 }
526
527 out
528 }
529}
530
531impl std::ops::Add for TimsSpectrumAnnotated {
532 type Output = Self;
533 fn add(self, other: Self) -> Self {
534
535 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
536 let mut spec_map: BTreeMap<i64, (u32, f64, PeakAnnotation, i64)> = BTreeMap::new();
537 let mean_scan_floor = ((self.scan as f64 + other.scan as f64) / 2.0) as u32;
538
539 for (tof, mz, intensity, annotation) in izip!(self.tof.iter(), self.spectrum.mz.iter(), self.spectrum.intensity.iter(), self.spectrum.annotations.iter()) {
540 let key = quantize(*mz);
541 spec_map.insert(key, (*tof, *intensity, annotation.clone(), 1));
542 }
543
544 for (tof, mz, intensity, annotation) in izip!(other.tof.iter(), other.spectrum.mz.iter(), other.spectrum.intensity.iter(), other.spectrum.annotations.iter()) {
545 let key = quantize(*mz);
546 spec_map.entry(key).and_modify(|e| {
547 e.0 += *tof;
548 e.1 += *intensity;
549 e.2.contributions.extend(annotation.contributions.clone());
550 e.3 += 1;
551 }).or_insert((*tof, *intensity, annotation.clone(), 1));
552 }
553
554 let mut tof_vec: Vec<u32> = Vec::with_capacity(spec_map.len());
555 let mut mz_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
556 let mut intensity_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
557 let mut annotations_vec: Vec<PeakAnnotation> = Vec::with_capacity(spec_map.len());
558
559 for (key, (tof, intensity, annotation, count)) in spec_map.iter() {
560 tof_vec.push((*tof as f64 / *count as f64) as u32);
561 mz_vec.push(*key as f64 / 1_000_000.0);
562 intensity_vec.push(*intensity / *count as f64);
563 annotations_vec.push(annotation.clone());
564 }
565
566 assert!(tof_vec.len() == mz_vec.len() && mz_vec.len() == intensity_vec.len() && intensity_vec.len() == annotations_vec.len());
567
568 TimsSpectrumAnnotated {
569 frame_id: self.frame_id,
570 scan: mean_scan_floor,
571 retention_time: self.retention_time,
572 mobility: self.mobility,
573 ms_type: if self.ms_type == other.ms_type { self.ms_type.clone() } else { MsType::Unknown },
574 tof: tof_vec,
575 spectrum: MzSpectrumAnnotated::new(mz_vec, intensity_vec, annotations_vec),
576 }
577 }
578}
579
580#[derive(Clone, Debug)]
581pub struct TimsFrameAnnotated {
582 pub frame_id: i32,
583 pub retention_time: f64,
584 pub ms_type: MsType,
585 pub tof: Vec<u32>,
586 pub mz: Vec<f64>,
587 pub scan: Vec<u32>,
588 pub inv_mobility: Vec<f64>,
589 pub intensity: Vec<f64>,
590 pub annotations: Vec<PeakAnnotation>,
591}
592
593impl TimsFrameAnnotated {
594 pub fn new(frame_id: i32, retention_time: f64, ms_type: MsType, tof: Vec<u32>, mz: Vec<f64>, scan: Vec<u32>, inv_mobility: Vec<f64>, intensity: Vec<f64>, annotations: Vec<PeakAnnotation>) -> Self {
595 assert!(tof.len() == mz.len() && mz.len() == scan.len() && scan.len() == inv_mobility.len() && inv_mobility.len() == intensity.len() && intensity.len() == annotations.len());
596 TimsFrameAnnotated {
597 frame_id,
598 retention_time,
599 ms_type,
600 tof,
601 mz,
602 scan,
603 inv_mobility,
604 intensity,
605 annotations,
606 }
607 }
608 pub fn filter_ranged(&self, mz_min: f64, mz_max: f64, inv_mobility_min: f64, inv_mobility_max: f64, scan_min: u32, scan_max: u32, intensity_min: f64, intensity_max: f64) -> Self {
609 let mut tof_filtered: Vec<u32> = Vec::new();
610 let mut mz_filtered: Vec<f64> = Vec::new();
611 let mut scan_filtered: Vec<u32> = Vec::new();
612 let mut inv_mobility_filtered: Vec<f64> = Vec::new();
613 let mut intensity_filtered: Vec<f64> = Vec::new();
614 let mut annotations_filtered: Vec<PeakAnnotation> = Vec::new();
615
616 for (tof, mz, scan, inv_mobility, intensity, annotation) in izip!(self.tof.iter(), self.mz.iter(), self.scan.iter(), self.inv_mobility.iter(), self.intensity.iter(), self.annotations.iter()) {
617 if *mz >= mz_min && *mz <= mz_max && *inv_mobility >= inv_mobility_min && *inv_mobility <= inv_mobility_max && *scan >= scan_min && *scan <= scan_max && *intensity >= intensity_min && *intensity <= intensity_max {
618 tof_filtered.push(*tof);
619 mz_filtered.push(*mz);
620 scan_filtered.push(*scan);
621 inv_mobility_filtered.push(*inv_mobility);
622 intensity_filtered.push(*intensity);
623 annotations_filtered.push(annotation.clone());
624 }
625 }
626
627 assert!(tof_filtered.len() == mz_filtered.len() && mz_filtered.len() == scan_filtered.len() && scan_filtered.len() == inv_mobility_filtered.len() && inv_mobility_filtered.len() == intensity_filtered.len() && intensity_filtered.len() == annotations_filtered.len());
628
629 TimsFrameAnnotated {
630 frame_id: self.frame_id,
631 retention_time: self.retention_time,
632 ms_type: self.ms_type.clone(),
633 tof: tof_filtered,
634 mz: mz_filtered,
635 scan: scan_filtered,
636 inv_mobility: inv_mobility_filtered,
637 intensity: intensity_filtered,
638 annotations: annotations_filtered,
639 }
640 }
641
642 pub fn to_tims_spectra_annotated(&self) -> Vec<TimsSpectrumAnnotated> {
643 let mut spectra = BTreeMap::<i32, (f64, Vec<u32>, MzSpectrumAnnotated)>::new();
645
646 for (scan, mobility, tof, mz, intensity, annotations) in izip!(self.scan.iter(), self.inv_mobility.iter(), self.tof.iter(), self.mz.iter(), self.intensity.iter(), self.annotations.iter()) {
648 let entry = spectra.entry(*scan as i32).or_insert_with(|| (*mobility, Vec::new(), MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())));
649 entry.1.push(*tof);
650 entry.2.mz.push(*mz);
651 entry.2.intensity.push(*intensity);
652 entry.2.annotations.push(annotations.clone());
653 }
654
655 let mut tims_spectra: Vec<TimsSpectrumAnnotated> = Vec::new();
657
658 for (scan, (mobility, tof, spectrum)) in spectra {
659 tims_spectra.push(TimsSpectrumAnnotated::new(self.frame_id, scan as u32, self.retention_time, mobility, self.ms_type.clone(), tof, spectrum));
660 }
661
662 tims_spectra
663 }
664
665 pub fn from_tims_spectra_annotated(spectra: Vec<TimsSpectrumAnnotated>) -> TimsFrameAnnotated {
666 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
667 let mut spec_map: BTreeMap<(u32, i64), (f64, u32, f64, PeakAnnotation, i64)> = BTreeMap::new();
668 let mut capacity_count = 0;
669
670 for spectrum in &spectra {
671 let inv_mobility = spectrum.mobility;
672 for (i, mz) in spectrum.spectrum.mz.iter().enumerate() {
673 let scan = spectrum.scan;
674 let tof = spectrum.tof[i];
675 let intensity = spectrum.spectrum.intensity[i];
676 let annotation = spectrum.spectrum.annotations[i].clone();
677 let key = (scan, quantize(*mz));
678 spec_map.entry(key).and_modify(|e| {
679 e.0 += intensity;
680 e.1 += tof;
681 e.2 += inv_mobility;
682 e.3.contributions.extend(annotation.contributions.clone());
683 e.4 += 1;
684 }).or_insert((intensity, tof, inv_mobility, annotation, 1));
685 capacity_count += 1;
686 }
687 }
688
689 let mut scan_vec: Vec<u32> = Vec::with_capacity(capacity_count);
690 let mut inv_mobility_vec: Vec<f64> = Vec::with_capacity(capacity_count);
691 let mut tof_vec: Vec<u32> = Vec::with_capacity(capacity_count);
692 let mut mz_vec: Vec<f64> = Vec::with_capacity(capacity_count);
693 let mut intensity_vec: Vec<f64> = Vec::with_capacity(capacity_count);
694 let mut annotations_vec: Vec<PeakAnnotation> = Vec::with_capacity(capacity_count);
695
696 for ((scan, mz), (intensity, tof, inv_mobility, annotation, count)) in spec_map.iter() {
697 scan_vec.push(*scan);
698 inv_mobility_vec.push(*inv_mobility / *count as f64);
699 tof_vec.push((*tof as f64 / *count as f64) as u32);
700 mz_vec.push(*mz as f64 / 1_000_000.0);
701 intensity_vec.push(*intensity);
702 annotations_vec.push(annotation.clone());
703 }
704
705 assert!(tof_vec.len() == mz_vec.len() && mz_vec.len() == scan_vec.len() && scan_vec.len() == inv_mobility_vec.len() && inv_mobility_vec.len() == intensity_vec.len() && intensity_vec.len() == annotations_vec.len());
706
707 TimsFrameAnnotated {
708 frame_id: spectra.first().unwrap().frame_id,
709 retention_time: spectra.first().unwrap().retention_time,
710 ms_type: spectra.first().unwrap().ms_type.clone(),
711 tof: tof_vec,
712 mz: mz_vec,
713 scan: scan_vec,
714 inv_mobility: inv_mobility_vec,
715 intensity: intensity_vec,
716 annotations: annotations_vec,
717 }
718 }
719 pub fn to_windows_indexed(
720 &self,
721 window_length: f64,
722 overlapping: bool,
723 min_peaks: usize,
724 min_intensity: f64
725 ) -> (Vec<u32>, Vec<i32>, Vec<TimsSpectrumAnnotated>) {
726 let spectra = self.to_tims_spectra_annotated();
728
729 let windows_per_scan: Vec<_> = spectra
731 .iter()
732 .map(|s| s.to_windows(window_length, overlapping, min_peaks, min_intensity))
733 .collect();
734
735 let mut scan_indices = Vec::new();
737 let mut window_indices = Vec::new();
738 let mut out_spectra = Vec::new();
739
740 for (spec, window_map) in spectra.iter().zip(windows_per_scan.iter()) {
741 for (&win_idx, win_spec) in window_map {
742 scan_indices.push(spec.scan);
743 window_indices.push(win_idx);
744 out_spectra.push(win_spec.clone());
745 }
746 }
747
748 (scan_indices, window_indices, out_spectra)
749 }
750
751 pub fn to_windows(
752 &self,
753 window_length: f64,
754 overlapping: bool,
755 min_peaks: usize,
756 min_intensity: f64
757 ) -> Vec<TimsSpectrumAnnotated> {
758 let spectra = self.to_tims_spectra_annotated();
760
761 let windows_per_scan: Vec<_> = spectra
763 .iter()
764 .map(|s| s.to_windows(window_length, overlapping, min_peaks, min_intensity))
765 .collect();
766
767 let mut out_spectra = Vec::new();
769 for (_, window_map) in spectra.iter().zip(windows_per_scan.iter()) {
770 for (_, win_spec) in window_map {
771 out_spectra.push(win_spec.clone());
772 }
773 }
774
775 out_spectra
776 }
777
778 pub fn to_dense_windows(
779 &self,
780 window_length: f64,
781 overlapping: bool,
782 min_peaks: usize,
783 min_intensity: f64,
784 resolution: i32
785 ) -> (Vec<f64>, Vec<i32>, Vec<i32>, usize, usize) {
786 let factor = 10f64.powi(resolution);
787 let n_cols = ((window_length * factor).round() + 1.0) as usize;
788
789 let (scan_indices, window_indices, spectra) =
791 self.to_windows_indexed(window_length, overlapping, min_peaks, min_intensity);
792
793 let vec_specs: Vec<_> = spectra
795 .iter()
796 .map(|ts| ts.spectrum.vectorized(resolution))
797 .collect();
798
799 let n_rows = spectra.len();
801 let mut matrix = vec![0.0; n_rows * n_cols];
802
803 for (row, ( &win_idx, vec_spec)) in
805 multizip((&window_indices, &vec_specs))
806 .enumerate()
807 {
808 let start_i = if win_idx >= 0 {
810 ((win_idx as f64 * window_length) * factor).round() as i32
811 } else {
812 ((((-win_idx) as f64 * window_length) - 0.5 * window_length) * factor)
814 .round() as i32
815 };
816
817 for (&idx, &val) in vec_spec.indices.iter().zip(&vec_spec.values) {
819 let col = (idx as i32 - start_i) as usize;
820 let flat_idx = row * n_cols + col;
821 matrix[flat_idx] = val;
822 }
823 }
824
825 let scan_indices: Vec<i32> = scan_indices.iter().map(|&scan| scan as i32).collect();
827
828 (matrix, scan_indices, window_indices, n_rows, n_cols)
829 }
830
831 pub fn to_dense_windows_with_labels(
842 &self,
843 window_length: f64,
844 overlapping: bool,
845 min_peaks: usize,
846 min_intensity: f64,
847 resolution: i32,
848 ) -> (
849 Vec<f64>, Vec<u32>, Vec<i32>, Vec<f64>, Vec<f64>, usize, usize, Vec<i32>, Vec<i32>, Vec<i32>, ) {
860 let factor = 10f64.powi(resolution);
861 let n_cols = ((window_length * factor).round() + 1.0) as usize;
862
863 let (scan_indices, window_indices, spectra) =
865 self.to_windows_indexed(window_length, overlapping, min_peaks, min_intensity);
866 let vec_specs: Vec<_> = spectra
867 .iter()
868 .map(|ts| ts.spectrum.vectorized(resolution))
869 .collect();
870
871 let n_rows = vec_specs.len();
872 let matrix_sz = n_rows * n_cols;
873
874 let mut intensities = vec![0.0_f64; matrix_sz];
876 let mut iso_labels = vec![-1_i32; matrix_sz];
877 let mut charge_labels = vec![-1_i32; matrix_sz];
878 let mut peptide_labels = vec![-1_i32; matrix_sz];
879 let mut mz_start = Vec::with_capacity(n_rows);
880 let mut ion_mobility_start = Vec::with_capacity(n_rows);
881
882 for (row, ((&win_idx, vec_spec), ts)) in
884 multizip((&window_indices, &vec_specs))
885 .zip(spectra.iter())
886 .enumerate()
887 {
888 let first_mz = ts.spectrum.mz.first().cloned().unwrap_or(0.0);
890 mz_start.push(first_mz);
891 ion_mobility_start.push(ts.mobility);
892
893 let mut feat_map = HashMap::<i32,i32>::new();
895 let mut next_feat = 0;
896
897 let start_i = if win_idx >= 0 {
899 ((win_idx as f64 * window_length) * factor).round() as i32
900 } else {
901 (((-win_idx) as f64 * window_length - 0.5 * window_length) * factor)
902 .round() as i32
903 };
904
905 for (&bin_idx, &val, annotation) in
907 izip!(&vec_spec.indices, &vec_spec.values, &vec_spec.annotations)
908 {
909 let col = (bin_idx as i32 - start_i) as usize;
910 let flat = row * n_cols + col;
911 intensities[flat] = val;
912
913 if let Some(best) = annotation
915 .contributions
916 .iter()
917 .max_by(|a, b| {
918 a.intensity_contribution
919 .partial_cmp(&b.intensity_contribution)
920 .unwrap()
921 })
922 {
923 match best.source_type {
924 SourceType::Signal => {
925 if let Some(sa) = &best.signal_attributes {
926 iso_labels[flat] = sa.isotope_peak;
927 charge_labels[flat] = sa.charge_state;
928 let old = sa.peptide_id;
930 let new = *feat_map.entry(old).or_insert_with(|| {
931 let i = next_feat; next_feat += 1; i.min(5)
932 });
933 peptide_labels[flat] = new;
934 }
935 }
936 SourceType::RandomNoise => {
937 iso_labels[flat] = -2;
938 charge_labels[flat] = -2;
939 peptide_labels[flat] = -2;
940 }
941 _ => { }
942 }
943 }
944 }
945 }
946
947 (
948 intensities,
949 scan_indices,
950 window_indices,
951 mz_start,
952 ion_mobility_start,
953 n_rows,
954 n_cols,
955 iso_labels,
956 charge_labels,
957 peptide_labels,
958 )
959 }
960
961 pub fn fold_along_scan_axis(self, fold_width: usize) -> TimsFrameAnnotated {
962 let spectra = self.to_tims_spectra_annotated();
964
965 let mut merged_spectra: BTreeMap<u32, TimsSpectrumAnnotated> = BTreeMap::new();
968 for spectrum in spectra {
969 let key = spectrum.scan / fold_width as u32;
970
971 if let Some(existing_spectrum) = merged_spectra.get_mut(&key) {
973
974 let merged_spectrum = existing_spectrum.clone() + spectrum;
975 *existing_spectrum = merged_spectrum;
977
978 } else {
979 merged_spectra.insert(key, spectrum);
981 }
982 }
983 TimsFrameAnnotated::from_tims_spectra_annotated(merged_spectra.into_values().collect())
985 }
986}
987
988impl std::ops::Add for TimsFrameAnnotated {
989 type Output = Self;
990 fn add(self, other: Self) -> Self {
991
992 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
993 let mut spec_map: BTreeMap<(u32, i64), (f64, u32, f64, PeakAnnotation, i64)> = BTreeMap::new();
994
995 for (scan, mz, tof, inv_mobility, intensity, annotation) in
996 izip!(self.scan.iter(), self.mz.iter(), self.tof.iter(), self.inv_mobility.iter(), self.intensity.iter(), self.annotations.iter()) {
997 let key = (*scan, quantize(*mz));
998 spec_map.insert(key, (*intensity, *tof, *inv_mobility, annotation.clone(), 1));
999 }
1000
1001 for (scan, mz, tof, inv_mobility, intensity, annotation) in
1002 izip!(other.scan.iter(), other.mz.iter(), other.tof.iter(), other.inv_mobility.iter(), other.intensity.iter(), other.annotations.iter()) {
1003 let key = (*scan, quantize(*mz));
1004 spec_map.entry(key).and_modify(|e| {
1005 e.0 += *intensity;
1006 e.1 += *tof;
1007 e.2 += *inv_mobility;
1008 e.3.contributions.extend(annotation.contributions.clone());
1009 e.4 += 1;
1010 }).or_insert((*intensity, *tof, *inv_mobility, annotation.clone(), 1));
1011 }
1012
1013 let mut tof_vec: Vec<u32> = Vec::with_capacity(spec_map.len());
1014 let mut mz_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
1015 let mut scan_vec: Vec<u32> = Vec::with_capacity(spec_map.len());
1016 let mut inv_mobility_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
1017 let mut intensity_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
1018 let mut annotations_vec: Vec<PeakAnnotation> = Vec::with_capacity(spec_map.len());
1019
1020 for ((scan, mz), (intensity, tof, inv_mobility, annotation, count)) in spec_map.iter() {
1021 scan_vec.push(*scan);
1022 mz_vec.push(*mz as f64 / 1_000_000.0);
1023 intensity_vec.push(*intensity);
1024 tof_vec.push((*tof as f64 / *count as f64) as u32);
1025 inv_mobility_vec.push(*inv_mobility / *count as f64);
1026 annotations_vec.push(annotation.clone());
1027 }
1028
1029 assert!(tof_vec.len() == mz_vec.len() && mz_vec.len() == scan_vec.len() && scan_vec.len() == inv_mobility_vec.len() && inv_mobility_vec.len() == intensity_vec.len() && intensity_vec.len() == annotations_vec.len());
1030
1031 TimsFrameAnnotated {
1032 frame_id: self.frame_id,
1033 retention_time: self.retention_time,
1034 ms_type: if self.ms_type == other.ms_type { self.ms_type.clone() } else { MsType::Unknown },
1035 tof: tof_vec,
1036 mz: mz_vec,
1037 scan: scan_vec,
1038 inv_mobility: inv_mobility_vec,
1039 intensity: intensity_vec,
1040 annotations: annotations_vec,
1041 }
1042 }
1043}
1044
1045#[cfg(test)]
1046mod new_sort_tests {
1047 use super::*;
1048
1049 fn ann(peptide_id: i32) -> PeakAnnotation {
1052 PeakAnnotation {
1053 contributions: vec![ContributionSource {
1054 intensity_contribution: 1.0,
1055 source_type: SourceType::Signal,
1056 signal_attributes: Some(SignalAttributes {
1057 charge_state: 1,
1058 peptide_id,
1059 isotope_peak: 0,
1060 fragment_kind: None,
1061 fragment_ordinal: None,
1062 }),
1063 }],
1064 }
1065 }
1066
1067 fn pids(spec: &MzSpectrumAnnotated) -> Vec<i32> {
1068 spec.annotations
1069 .iter()
1070 .map(|a| a.contributions[0].signal_attributes.as_ref().unwrap().peptide_id)
1071 .collect()
1072 }
1073
1074 #[test]
1078 fn new_sorts_by_mz_and_keeps_annotation_attached() {
1079 let spec = MzSpectrumAnnotated::new(
1081 vec![300.0, 100.0, 200.0],
1082 vec![3.0, 1.0, 2.0],
1083 vec![ann(3000), ann(1000), ann(2000)],
1084 );
1085
1086 assert_eq!(spec.mz, vec![100.0, 200.0, 300.0]);
1087 assert_eq!(spec.intensity, vec![1.0, 2.0, 3.0]);
1088 assert_eq!(pids(&spec), vec![1000, 2000, 3000]);
1089 }
1090
1091 #[test]
1094 fn new_is_stable_for_equal_mz() {
1095 let spec = MzSpectrumAnnotated::new(
1096 vec![150.0, 150.0],
1097 vec![10.0, 20.0],
1098 vec![ann(111), ann(222)],
1099 );
1100 assert_eq!(spec.intensity, vec![10.0, 20.0]);
1101 assert_eq!(pids(&spec), vec![111, 222]);
1102 }
1103}