1use std::collections::{BTreeMap, HashMap};
2use std::fmt::Display;
3use itertools::{izip, multizip};
4use rand::distributions::{Uniform, Distribution};
5use rand::Rng;
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_uniform_with_rng(ppm, right_drag, &mut rng)
152 }
153
154 pub fn add_mz_noise_uniform_with_rng<R: Rng>(&self, ppm: f64, right_drag: bool, rng: &mut R) -> Self {
156 self.add_mz_noise(ppm, rng, |rng, mz, ppm| {
157
158 let ppm_mz = match right_drag {
159 true => mz * ppm / 1e6 / 2.0,
160 false => mz * ppm / 1e6,
161 };
162
163 let dist = match right_drag {
164 true => Uniform::from(mz - (ppm_mz / 3.0)..=mz + ppm_mz),
165 false => Uniform::from(mz - ppm_mz..=mz + ppm_mz),
166 };
167
168 dist.sample(rng)
169 })
170 }
171
172 pub fn add_mz_noise_normal(&self, ppm: f64) -> Self {
173 let mut rng = rand::thread_rng();
174 self.add_mz_noise_normal_with_rng(ppm, &mut rng)
175 }
176
177 pub fn add_mz_noise_normal_with_rng<R: Rng>(&self, ppm: f64, rng: &mut R) -> Self {
179 self.add_mz_noise(ppm, rng, |rng, mz, ppm| {
180 let ppm_mz = mz * ppm / 1e6;
181 let dist = Normal::new(mz, ppm_mz / 3.0).unwrap(); dist.sample(rng)
183 })
184 }
185
186 fn add_mz_noise<R: Rng, F>(&self, ppm: f64, rng: &mut R, noise_fn: F) -> Self
187 where
188 F: Fn(&mut R, f64, f64) -> f64,
189 {
190 let mz: Vec<f64> = self.mz.iter().map(|&mz_value| noise_fn(rng, mz_value, ppm)).collect();
191 let spectrum = MzSpectrumAnnotated { mz, intensity: self.intensity.clone(), annotations: self.annotations.clone()};
192
193 spectrum.to_resolution(6)
195 }
196
197 pub fn to_windows(&self, window_length: f64, overlapping: bool, min_peaks: usize, min_intensity: f64) -> BTreeMap<i32, MzSpectrumAnnotated> {
198 let mut splits = BTreeMap::new();
199
200 for (i, &mz) in self.mz.iter().enumerate() {
201 let intensity = self.intensity[i];
202 let annotation = self.annotations[i].clone();
203
204 let tmp_key = (mz / window_length).floor() as i32;
205
206 splits.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).mz.push(mz);
207 splits.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).intensity.push(intensity);
208 splits.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).annotations.push(annotation);
209 }
210
211 if overlapping {
212 let mut splits_offset = BTreeMap::new();
213
214 for (i, &mmz) in self.mz.iter().enumerate() {
215 let intensity = self.intensity[i];
216 let annotation = self.annotations[i].clone();
217
218 let tmp_key = -((mmz + window_length / 2.0) / window_length).floor() as i32;
219
220 splits_offset.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).mz.push(mmz);
221 splits_offset.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).intensity.push(intensity);
222 splits_offset.entry(tmp_key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).annotations.push(annotation);
223 }
224
225 for (key, val) in splits_offset {
226 splits.entry(key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).mz.extend(val.mz);
227 splits.entry(key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).intensity.extend(val.intensity);
228 splits.entry(key).or_insert_with(|| MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())).annotations.extend(val.annotations);
229 }
230 }
231
232 splits.retain(|_, spectrum| {
233 spectrum.mz.len() >= min_peaks && spectrum.intensity.iter().cloned().max_by(
234 |a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap_or(0.0) >= min_intensity
235 });
236
237 splits
238 }
239}
240
241impl std::ops::Add for MzSpectrumAnnotated {
242 type Output = Self;
243 fn add(self, other: Self) -> Self {
244
245 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
246 let mut spec_map: BTreeMap<i64, (f64, PeakAnnotation)> = BTreeMap::new();
247
248 for ((mz, intensity), annotation) in self.mz.iter().zip(self.intensity.iter()).zip(self.annotations.iter()) {
249 let key = quantize(*mz);
250 spec_map.insert(key, (*intensity, annotation.clone()));
251 }
252
253 for ((mz, intensity), annotation) in other.mz.iter().zip(other.intensity.iter()).zip(other.annotations.iter()) {
254 let key = quantize(*mz);
255 spec_map.entry(key).and_modify(|e| {
256 e.0 += *intensity;
257 e.1.contributions.extend(annotation.contributions.clone());
258 }).or_insert((*intensity, annotation.clone()));
259 }
260
261 let mz: Vec<f64> = spec_map.keys().map(|&key| key as f64 / 1_000_000.0).collect();
262 let intensity: Vec<f64> = spec_map.values().map(|(intensity, _)| *intensity).collect();
263 let annotations: Vec<PeakAnnotation> = spec_map.values().map(|(_, annotation)| annotation.clone()).collect();
264
265 assert!(mz.len() == intensity.len() && intensity.len() == annotations.len());
266
267 MzSpectrumAnnotated {
268 mz,
269 intensity,
270 annotations,
271 }
272 }
273}
274
275impl ToResolution for MzSpectrumAnnotated {
276 fn to_resolution(&self, resolution: i32) -> Self {
277 let mut spec_map: BTreeMap<i64, (f64, PeakAnnotation)> = BTreeMap::new();
278 let quantize = |mz: f64| -> i64 { (mz * 10.0_f64.powi(resolution)).round() as i64 };
279
280 for ((mz, intensity), annotation) in self.mz.iter().zip(self.intensity.iter()).zip(self.annotations.iter()) {
281 let key = quantize(*mz);
282 spec_map.entry(key).and_modify(|e| {
283 e.0 += *intensity;
284 e.1.contributions.extend(annotation.contributions.clone());
285 }).or_insert((*intensity, annotation.clone()));
286 }
287
288 let mz: Vec<f64> = spec_map.keys().map(|&key| key as f64 / 10.0_f64.powi(resolution)).collect();
289 let intensity: Vec<f64> = spec_map.values().map(|(intensity, _)| *intensity).collect();
290 let annotations: Vec<PeakAnnotation> = spec_map.values().map(|(_, annotation)| annotation.clone()).collect();
291
292 assert!(mz.len() == intensity.len() && intensity.len() == annotations.len());
293
294 MzSpectrumAnnotated {
295 mz,
296 intensity,
297 annotations,
298 }
299 }
300}
301
302impl std::ops::Mul<f64> for MzSpectrumAnnotated {
303 type Output = Self;
304 fn mul(self, scale: f64) -> Self::Output{
305
306 let mut scaled_intensities: Vec<f64> = vec![0.0; self.intensity.len()];
307
308 for (idx,intensity) in self.intensity.iter().enumerate(){
309 scaled_intensities[idx] = scale*intensity;
310 }
311
312 let mut scaled_annotations: Vec<PeakAnnotation> = Vec::new();
313
314 for annotation in self.annotations.iter(){
315 let mut scaled_contributions: Vec<ContributionSource> = Vec::new();
316 for contribution in annotation.contributions.iter(){
317 let scaled_intensity = (contribution.intensity_contribution*scale).round();
318 let scaled_contribution = ContributionSource{
319 intensity_contribution: scaled_intensity,
320 source_type: contribution.source_type.clone(),
321 signal_attributes: contribution.signal_attributes.clone(),
322 };
323 scaled_contributions.push(scaled_contribution);
324 }
325 let scaled_annotation = PeakAnnotation{
326 contributions: scaled_contributions,
327 };
328 scaled_annotations.push(scaled_annotation);
329 }
330
331 MzSpectrumAnnotated { mz: self.mz.clone(), intensity: scaled_intensities, annotations: scaled_annotations }
332 }
333}
334
335impl Vectorized<MzSpectrumAnnotatedVectorized> for MzSpectrumAnnotated {
336 fn vectorized(&self, resolution: i32) -> MzSpectrumAnnotatedVectorized {
337
338 let quantize = |mz: f64| -> i64 { (mz * 10.0_f64.powi(resolution)).round() as i64 };
339
340 let binned_spec = self.to_resolution(resolution);
341 let mut indices: Vec<u32> = Vec::with_capacity(binned_spec.mz.len());
342 let mut values: Vec<f64> = Vec::with_capacity(binned_spec.mz.len());
343 let mut annotations: Vec<PeakAnnotation> = Vec::with_capacity(binned_spec.mz.len());
344
345 for (mz, intensity, annotation) in izip!(binned_spec.mz.iter(), binned_spec.intensity.iter(), binned_spec.annotations.iter()) {
346 indices.push(quantize(*mz) as u32);
347 values.push(*intensity);
348 annotations.push(annotation.clone());
349 }
350
351 MzSpectrumAnnotatedVectorized {
352 indices,
353 values,
354 annotations,
355 }
356 }
357}
358
359#[derive(Clone, Debug)]
360pub struct MzSpectrumAnnotatedVectorized {
361 pub indices: Vec<u32>,
362 pub values: Vec<f64>,
363 pub annotations: Vec<PeakAnnotation>,
364}
365
366#[derive(Clone, Debug)]
367pub struct TimsSpectrumAnnotated {
368 pub frame_id: i32,
369 pub scan: u32,
370 pub retention_time: f64,
371 pub mobility: f64,
372 pub ms_type: MsType,
373 pub tof: Vec<u32>,
374 pub spectrum: MzSpectrumAnnotated,
375}
376
377impl TimsSpectrumAnnotated {
378 pub fn new(frame_id: i32, scan: u32, retention_time: f64, mobility: f64, ms_type: MsType, tof: Vec<u32>, spectrum: MzSpectrumAnnotated) -> Self {
379 assert!(tof.len() == spectrum.mz.len() && spectrum.mz.len() == spectrum.intensity.len() && spectrum.intensity.len() == spectrum.annotations.len());
380 let mut mz_intensity_annotations: Vec<(u32, f64, f64, PeakAnnotation)> = izip!(tof.iter(), spectrum.mz.iter(), spectrum.intensity.iter(),
382 spectrum.annotations.iter()).map(|(tof, mz, intensity, annotation)| (*tof, *mz, *intensity, annotation.clone())).collect();
383 mz_intensity_annotations.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
384 TimsSpectrumAnnotated {
385 frame_id,
386 scan,
387 retention_time,
388 mobility,
389 ms_type,
390 tof: mz_intensity_annotations.iter().map(|(tof, _, _, _)| *tof).collect(),
391 spectrum: MzSpectrumAnnotated {
392 mz: mz_intensity_annotations.iter().map(|(_, mz, _, _)| *mz).collect(),
393 intensity: mz_intensity_annotations.iter().map(|(_, _, intensity, _)| *intensity).collect(),
394 annotations: mz_intensity_annotations.iter().map(|(_, _, _, annotation)| annotation.clone()).collect(),
395 },
396 }
397 }
398
399 pub fn filter_ranged(&self, mz_min: f64, mz_max: f64, intensity_min: f64, intensity_max: f64) -> Self {
400 let mut tof_filtered: Vec<u32> = Vec::new();
401 let mut mz_filtered: Vec<f64> = Vec::new();
402 let mut intensity_filtered: Vec<f64> = Vec::new();
403 let mut annotations_filtered: Vec<PeakAnnotation> = Vec::new();
404
405 for (tof, mz, intensity, annotation) in izip!(self.tof.iter(), self.spectrum.mz.iter(), self.spectrum.intensity.iter(), self.spectrum.annotations.iter()) {
406 if *mz >= mz_min && *mz <= mz_max && *intensity >= intensity_min && *intensity <= intensity_max {
407 tof_filtered.push(*tof);
408 mz_filtered.push(*mz);
409 intensity_filtered.push(*intensity);
410 annotations_filtered.push(annotation.clone());
411 }
412 }
413
414 assert!(tof_filtered.len() == mz_filtered.len() && mz_filtered.len() == intensity_filtered.len() && intensity_filtered.len() == annotations_filtered.len());
415
416 TimsSpectrumAnnotated {
417 frame_id: self.frame_id,
418 scan: self.scan,
419 retention_time: self.retention_time,
420 mobility: self.mobility,
421 ms_type: self.ms_type.clone(),
422 tof: tof_filtered,
423 spectrum: MzSpectrumAnnotated::new(mz_filtered, intensity_filtered, annotations_filtered),
424 }
425 }
426
427 pub fn add_mz_noise_uniform(&self, ppm: f64, right_drag: bool) -> Self {
428 TimsSpectrumAnnotated {
429 frame_id: self.frame_id,
430 scan: self.scan,
431 retention_time: self.retention_time,
432 mobility: self.mobility,
433 ms_type: self.ms_type.clone(),
434 tof: self.tof.clone(),
436 spectrum: self.spectrum.add_mz_noise_uniform(ppm, right_drag),
437 }
438 }
439
440 pub fn add_mz_noise_normal(&self, ppm: f64) -> Self {
441 TimsSpectrumAnnotated {
442 frame_id: self.frame_id,
443 scan: self.scan,
444 retention_time: self.retention_time,
445 mobility: self.mobility,
446 ms_type: self.ms_type.clone(),
447 tof: self.tof.clone(),
449 spectrum: self.spectrum.add_mz_noise_normal(ppm),
450 }
451 }
452
453 pub fn to_windows(
454 &self,
455 window_length: f64,
456 overlapping: bool,
457 min_peaks: usize,
458 min_intensity: f64,
459 ) -> BTreeMap<i32, TimsSpectrumAnnotated> {
460 let mut buckets: BTreeMap<i32, Vec<(u32, f64, f64, PeakAnnotation)>> = BTreeMap::new();
462 for (&tof, &mz, &intensity, annotation) in
463 izip!(
464 &self.tof,
465 &self.spectrum.mz,
466 &self.spectrum.intensity,
467 &self.spectrum.annotations
468 )
469 {
470 let idx = (mz / window_length).floor() as i32;
471 buckets.entry(idx)
472 .or_default()
473 .push((tof, mz, intensity, annotation.clone()));
474 }
475
476 if overlapping {
478 let mut off: BTreeMap<i32, Vec<(u32, f64, f64, PeakAnnotation)>> = BTreeMap::new();
479 let half = window_length / 2.0;
480 for (&tof, &mz, &intensity, annotation) in
481 izip!(
482 &self.tof,
483 &self.spectrum.mz,
484 &self.spectrum.intensity,
485 &self.spectrum.annotations
486 )
487 {
488 let idx = -(((mz + half) / window_length).floor() as i32);
489 off.entry(idx)
490 .or_default()
491 .push((tof, mz, intensity, annotation.clone()));
492 }
493 for (k, v) in off {
494 buckets.entry(k).or_default().extend(v);
495 }
496 }
497
498 let mut out = BTreeMap::new();
500 for (idx, group) in buckets {
501 if group.len() < min_peaks {
502 continue;
503 }
504 let max_i = group.iter().map(|&(_, _, i, _)| i).fold(0.0, f64::max);
505 if max_i < min_intensity {
506 continue;
507 }
508
509 let mut tofs = Vec::with_capacity(group.len());
511 let mut mzs = Vec::with_capacity(group.len());
512 let mut ints = Vec::with_capacity(group.len());
513 let mut annots = Vec::with_capacity(group.len());
514 for (tof, mz, intensity, annotation) in group {
515 tofs.push(tof);
516 mzs.push(mz);
517 ints.push(intensity);
518 annots.push(annotation);
519 }
520
521 let window_spec = MzSpectrumAnnotated::new(mzs, ints, annots);
523
524 let sub = TimsSpectrumAnnotated {
525 frame_id: self.frame_id,
526 scan: self.scan,
527 retention_time: self.retention_time,
528 mobility: self.mobility,
529 ms_type: self.ms_type.clone(),
530 tof: tofs,
531 spectrum: window_spec,
532 };
533
534 out.insert(idx, sub);
535 }
536
537 out
538 }
539}
540
541impl std::ops::Add for TimsSpectrumAnnotated {
542 type Output = Self;
543 fn add(self, other: Self) -> Self {
544
545 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
546 let mut spec_map: BTreeMap<i64, (u32, f64, PeakAnnotation, i64)> = BTreeMap::new();
547 let mean_scan_floor = ((self.scan as f64 + other.scan as f64) / 2.0) as u32;
548
549 for (tof, mz, intensity, annotation) in izip!(self.tof.iter(), self.spectrum.mz.iter(), self.spectrum.intensity.iter(), self.spectrum.annotations.iter()) {
550 let key = quantize(*mz);
551 spec_map.insert(key, (*tof, *intensity, annotation.clone(), 1));
552 }
553
554 for (tof, mz, intensity, annotation) in izip!(other.tof.iter(), other.spectrum.mz.iter(), other.spectrum.intensity.iter(), other.spectrum.annotations.iter()) {
555 let key = quantize(*mz);
556 spec_map.entry(key).and_modify(|e| {
557 e.0 += *tof;
558 e.1 += *intensity;
559 e.2.contributions.extend(annotation.contributions.clone());
560 e.3 += 1;
561 }).or_insert((*tof, *intensity, annotation.clone(), 1));
562 }
563
564 let mut tof_vec: Vec<u32> = Vec::with_capacity(spec_map.len());
565 let mut mz_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
566 let mut intensity_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
567 let mut annotations_vec: Vec<PeakAnnotation> = Vec::with_capacity(spec_map.len());
568
569 for (key, (tof, intensity, annotation, count)) in spec_map.iter() {
570 tof_vec.push((*tof as f64 / *count as f64) as u32);
571 mz_vec.push(*key as f64 / 1_000_000.0);
572 intensity_vec.push(*intensity / *count as f64);
573 annotations_vec.push(annotation.clone());
574 }
575
576 assert!(tof_vec.len() == mz_vec.len() && mz_vec.len() == intensity_vec.len() && intensity_vec.len() == annotations_vec.len());
577
578 TimsSpectrumAnnotated {
579 frame_id: self.frame_id,
580 scan: mean_scan_floor,
581 retention_time: self.retention_time,
582 mobility: self.mobility,
583 ms_type: if self.ms_type == other.ms_type { self.ms_type.clone() } else { MsType::Unknown },
584 tof: tof_vec,
585 spectrum: MzSpectrumAnnotated::new(mz_vec, intensity_vec, annotations_vec),
586 }
587 }
588}
589
590#[derive(Clone, Debug)]
591pub struct TimsFrameAnnotated {
592 pub frame_id: i32,
593 pub retention_time: f64,
594 pub ms_type: MsType,
595 pub tof: Vec<u32>,
596 pub mz: Vec<f64>,
597 pub scan: Vec<u32>,
598 pub inv_mobility: Vec<f64>,
599 pub intensity: Vec<f64>,
600 pub annotations: Vec<PeakAnnotation>,
601}
602
603impl TimsFrameAnnotated {
604 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 {
605 assert!(tof.len() == mz.len() && mz.len() == scan.len() && scan.len() == inv_mobility.len() && inv_mobility.len() == intensity.len() && intensity.len() == annotations.len());
606 TimsFrameAnnotated {
607 frame_id,
608 retention_time,
609 ms_type,
610 tof,
611 mz,
612 scan,
613 inv_mobility,
614 intensity,
615 annotations,
616 }
617 }
618 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 {
619 let mut tof_filtered: Vec<u32> = Vec::new();
620 let mut mz_filtered: Vec<f64> = Vec::new();
621 let mut scan_filtered: Vec<u32> = Vec::new();
622 let mut inv_mobility_filtered: Vec<f64> = Vec::new();
623 let mut intensity_filtered: Vec<f64> = Vec::new();
624 let mut annotations_filtered: Vec<PeakAnnotation> = Vec::new();
625
626 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()) {
627 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 {
628 tof_filtered.push(*tof);
629 mz_filtered.push(*mz);
630 scan_filtered.push(*scan);
631 inv_mobility_filtered.push(*inv_mobility);
632 intensity_filtered.push(*intensity);
633 annotations_filtered.push(annotation.clone());
634 }
635 }
636
637 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());
638
639 TimsFrameAnnotated {
640 frame_id: self.frame_id,
641 retention_time: self.retention_time,
642 ms_type: self.ms_type.clone(),
643 tof: tof_filtered,
644 mz: mz_filtered,
645 scan: scan_filtered,
646 inv_mobility: inv_mobility_filtered,
647 intensity: intensity_filtered,
648 annotations: annotations_filtered,
649 }
650 }
651
652 pub fn to_tims_spectra_annotated(&self) -> Vec<TimsSpectrumAnnotated> {
653 let mut spectra = BTreeMap::<i32, (f64, Vec<u32>, MzSpectrumAnnotated)>::new();
655
656 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()) {
658 let entry = spectra.entry(*scan as i32).or_insert_with(|| (*mobility, Vec::new(), MzSpectrumAnnotated::new(Vec::new(), Vec::new(), Vec::new())));
659 entry.1.push(*tof);
660 entry.2.mz.push(*mz);
661 entry.2.intensity.push(*intensity);
662 entry.2.annotations.push(annotations.clone());
663 }
664
665 let mut tims_spectra: Vec<TimsSpectrumAnnotated> = Vec::new();
667
668 for (scan, (mobility, tof, spectrum)) in spectra {
669 tims_spectra.push(TimsSpectrumAnnotated::new(self.frame_id, scan as u32, self.retention_time, mobility, self.ms_type.clone(), tof, spectrum));
670 }
671
672 tims_spectra
673 }
674
675 pub fn from_tims_spectra_annotated(spectra: Vec<TimsSpectrumAnnotated>) -> TimsFrameAnnotated {
676 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
677 let mut spec_map: BTreeMap<(u32, i64), (f64, u32, f64, PeakAnnotation, i64)> = BTreeMap::new();
678 let mut capacity_count = 0;
679
680 for spectrum in &spectra {
681 let inv_mobility = spectrum.mobility;
682 for (i, mz) in spectrum.spectrum.mz.iter().enumerate() {
683 let scan = spectrum.scan;
684 let tof = spectrum.tof[i];
685 let intensity = spectrum.spectrum.intensity[i];
686 let annotation = spectrum.spectrum.annotations[i].clone();
687 let key = (scan, quantize(*mz));
688 spec_map.entry(key).and_modify(|e| {
689 e.0 += intensity;
690 e.1 += tof;
691 e.2 += inv_mobility;
692 e.3.contributions.extend(annotation.contributions.clone());
693 e.4 += 1;
694 }).or_insert((intensity, tof, inv_mobility, annotation, 1));
695 capacity_count += 1;
696 }
697 }
698
699 let mut scan_vec: Vec<u32> = Vec::with_capacity(capacity_count);
700 let mut inv_mobility_vec: Vec<f64> = Vec::with_capacity(capacity_count);
701 let mut tof_vec: Vec<u32> = Vec::with_capacity(capacity_count);
702 let mut mz_vec: Vec<f64> = Vec::with_capacity(capacity_count);
703 let mut intensity_vec: Vec<f64> = Vec::with_capacity(capacity_count);
704 let mut annotations_vec: Vec<PeakAnnotation> = Vec::with_capacity(capacity_count);
705
706 for ((scan, mz), (intensity, tof, inv_mobility, annotation, count)) in spec_map.iter() {
707 scan_vec.push(*scan);
708 inv_mobility_vec.push(*inv_mobility / *count as f64);
709 tof_vec.push((*tof as f64 / *count as f64) as u32);
710 mz_vec.push(*mz as f64 / 1_000_000.0);
711 intensity_vec.push(*intensity);
712 annotations_vec.push(annotation.clone());
713 }
714
715 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());
716
717 TimsFrameAnnotated {
718 frame_id: spectra.first().unwrap().frame_id,
719 retention_time: spectra.first().unwrap().retention_time,
720 ms_type: spectra.first().unwrap().ms_type.clone(),
721 tof: tof_vec,
722 mz: mz_vec,
723 scan: scan_vec,
724 inv_mobility: inv_mobility_vec,
725 intensity: intensity_vec,
726 annotations: annotations_vec,
727 }
728 }
729 pub fn to_windows_indexed(
730 &self,
731 window_length: f64,
732 overlapping: bool,
733 min_peaks: usize,
734 min_intensity: f64
735 ) -> (Vec<u32>, Vec<i32>, Vec<TimsSpectrumAnnotated>) {
736 let spectra = self.to_tims_spectra_annotated();
738
739 let windows_per_scan: Vec<_> = spectra
741 .iter()
742 .map(|s| s.to_windows(window_length, overlapping, min_peaks, min_intensity))
743 .collect();
744
745 let mut scan_indices = Vec::new();
747 let mut window_indices = Vec::new();
748 let mut out_spectra = Vec::new();
749
750 for (spec, window_map) in spectra.iter().zip(windows_per_scan.iter()) {
751 for (&win_idx, win_spec) in window_map {
752 scan_indices.push(spec.scan);
753 window_indices.push(win_idx);
754 out_spectra.push(win_spec.clone());
755 }
756 }
757
758 (scan_indices, window_indices, out_spectra)
759 }
760
761 pub fn to_windows(
762 &self,
763 window_length: f64,
764 overlapping: bool,
765 min_peaks: usize,
766 min_intensity: f64
767 ) -> Vec<TimsSpectrumAnnotated> {
768 let spectra = self.to_tims_spectra_annotated();
770
771 let windows_per_scan: Vec<_> = spectra
773 .iter()
774 .map(|s| s.to_windows(window_length, overlapping, min_peaks, min_intensity))
775 .collect();
776
777 let mut out_spectra = Vec::new();
779 for (_, window_map) in spectra.iter().zip(windows_per_scan.iter()) {
780 for (_, win_spec) in window_map {
781 out_spectra.push(win_spec.clone());
782 }
783 }
784
785 out_spectra
786 }
787
788 pub fn to_dense_windows(
789 &self,
790 window_length: f64,
791 overlapping: bool,
792 min_peaks: usize,
793 min_intensity: f64,
794 resolution: i32
795 ) -> (Vec<f64>, Vec<i32>, Vec<i32>, usize, usize) {
796 let factor = 10f64.powi(resolution);
797 let n_cols = ((window_length * factor).round() + 1.0) as usize;
798
799 let (scan_indices, window_indices, spectra) =
801 self.to_windows_indexed(window_length, overlapping, min_peaks, min_intensity);
802
803 let vec_specs: Vec<_> = spectra
805 .iter()
806 .map(|ts| ts.spectrum.vectorized(resolution))
807 .collect();
808
809 let n_rows = spectra.len();
811 let mut matrix = vec![0.0; n_rows * n_cols];
812
813 for (row, ( &win_idx, vec_spec)) in
815 multizip((&window_indices, &vec_specs))
816 .enumerate()
817 {
818 let start_i = if win_idx >= 0 {
820 ((win_idx as f64 * window_length) * factor).round() as i32
821 } else {
822 ((((-win_idx) as f64 * window_length) - 0.5 * window_length) * factor)
824 .round() as i32
825 };
826
827 for (&idx, &val) in vec_spec.indices.iter().zip(&vec_spec.values) {
829 let col = (idx as i32 - start_i) as usize;
830 let flat_idx = row * n_cols + col;
831 matrix[flat_idx] = val;
832 }
833 }
834
835 let scan_indices: Vec<i32> = scan_indices.iter().map(|&scan| scan as i32).collect();
837
838 (matrix, scan_indices, window_indices, n_rows, n_cols)
839 }
840
841 pub fn to_dense_windows_with_labels(
852 &self,
853 window_length: f64,
854 overlapping: bool,
855 min_peaks: usize,
856 min_intensity: f64,
857 resolution: i32,
858 ) -> (
859 Vec<f64>, Vec<u32>, Vec<i32>, Vec<f64>, Vec<f64>, usize, usize, Vec<i32>, Vec<i32>, Vec<i32>, ) {
870 let factor = 10f64.powi(resolution);
871 let n_cols = ((window_length * factor).round() + 1.0) as usize;
872
873 let (scan_indices, window_indices, spectra) =
875 self.to_windows_indexed(window_length, overlapping, min_peaks, min_intensity);
876 let vec_specs: Vec<_> = spectra
877 .iter()
878 .map(|ts| ts.spectrum.vectorized(resolution))
879 .collect();
880
881 let n_rows = vec_specs.len();
882 let matrix_sz = n_rows * n_cols;
883
884 let mut intensities = vec![0.0_f64; matrix_sz];
886 let mut iso_labels = vec![-1_i32; matrix_sz];
887 let mut charge_labels = vec![-1_i32; matrix_sz];
888 let mut peptide_labels = vec![-1_i32; matrix_sz];
889 let mut mz_start = Vec::with_capacity(n_rows);
890 let mut ion_mobility_start = Vec::with_capacity(n_rows);
891
892 for (row, ((&win_idx, vec_spec), ts)) in
894 multizip((&window_indices, &vec_specs))
895 .zip(spectra.iter())
896 .enumerate()
897 {
898 let first_mz = ts.spectrum.mz.first().cloned().unwrap_or(0.0);
900 mz_start.push(first_mz);
901 ion_mobility_start.push(ts.mobility);
902
903 let mut feat_map = HashMap::<i32,i32>::new();
905 let mut next_feat = 0;
906
907 let start_i = if win_idx >= 0 {
909 ((win_idx as f64 * window_length) * factor).round() as i32
910 } else {
911 (((-win_idx) as f64 * window_length - 0.5 * window_length) * factor)
912 .round() as i32
913 };
914
915 for (&bin_idx, &val, annotation) in
917 izip!(&vec_spec.indices, &vec_spec.values, &vec_spec.annotations)
918 {
919 let col = (bin_idx as i32 - start_i) as usize;
920 let flat = row * n_cols + col;
921 intensities[flat] = val;
922
923 if let Some(best) = annotation
925 .contributions
926 .iter()
927 .max_by(|a, b| {
928 a.intensity_contribution
929 .partial_cmp(&b.intensity_contribution)
930 .unwrap()
931 })
932 {
933 match best.source_type {
934 SourceType::Signal => {
935 if let Some(sa) = &best.signal_attributes {
936 iso_labels[flat] = sa.isotope_peak;
937 charge_labels[flat] = sa.charge_state;
938 let old = sa.peptide_id;
940 let new = *feat_map.entry(old).or_insert_with(|| {
941 let i = next_feat; next_feat += 1; i.min(5)
942 });
943 peptide_labels[flat] = new;
944 }
945 }
946 SourceType::RandomNoise => {
947 iso_labels[flat] = -2;
948 charge_labels[flat] = -2;
949 peptide_labels[flat] = -2;
950 }
951 _ => { }
952 }
953 }
954 }
955 }
956
957 (
958 intensities,
959 scan_indices,
960 window_indices,
961 mz_start,
962 ion_mobility_start,
963 n_rows,
964 n_cols,
965 iso_labels,
966 charge_labels,
967 peptide_labels,
968 )
969 }
970
971 pub fn fold_along_scan_axis(self, fold_width: usize) -> TimsFrameAnnotated {
972 let spectra = self.to_tims_spectra_annotated();
974
975 let mut merged_spectra: BTreeMap<u32, TimsSpectrumAnnotated> = BTreeMap::new();
978 for spectrum in spectra {
979 let key = spectrum.scan / fold_width as u32;
980
981 if let Some(existing_spectrum) = merged_spectra.get_mut(&key) {
983
984 let merged_spectrum = existing_spectrum.clone() + spectrum;
985 *existing_spectrum = merged_spectrum;
987
988 } else {
989 merged_spectra.insert(key, spectrum);
991 }
992 }
993 TimsFrameAnnotated::from_tims_spectra_annotated(merged_spectra.into_values().collect())
995 }
996}
997
998impl std::ops::Add for TimsFrameAnnotated {
999 type Output = Self;
1000 fn add(self, other: Self) -> Self {
1001
1002 let quantize = |mz: f64| -> i64 { (mz * 1_000_000.0).round() as i64 };
1003 let mut spec_map: BTreeMap<(u32, i64), (f64, u32, f64, PeakAnnotation, i64)> = BTreeMap::new();
1004
1005 for (scan, mz, tof, inv_mobility, intensity, annotation) in
1006 izip!(self.scan.iter(), self.mz.iter(), self.tof.iter(), self.inv_mobility.iter(), self.intensity.iter(), self.annotations.iter()) {
1007 let key = (*scan, quantize(*mz));
1008 spec_map.insert(key, (*intensity, *tof, *inv_mobility, annotation.clone(), 1));
1009 }
1010
1011 for (scan, mz, tof, inv_mobility, intensity, annotation) in
1012 izip!(other.scan.iter(), other.mz.iter(), other.tof.iter(), other.inv_mobility.iter(), other.intensity.iter(), other.annotations.iter()) {
1013 let key = (*scan, quantize(*mz));
1014 spec_map.entry(key).and_modify(|e| {
1015 e.0 += *intensity;
1016 e.1 += *tof;
1017 e.2 += *inv_mobility;
1018 e.3.contributions.extend(annotation.contributions.clone());
1019 e.4 += 1;
1020 }).or_insert((*intensity, *tof, *inv_mobility, annotation.clone(), 1));
1021 }
1022
1023 let mut tof_vec: Vec<u32> = Vec::with_capacity(spec_map.len());
1024 let mut mz_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
1025 let mut scan_vec: Vec<u32> = Vec::with_capacity(spec_map.len());
1026 let mut inv_mobility_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
1027 let mut intensity_vec: Vec<f64> = Vec::with_capacity(spec_map.len());
1028 let mut annotations_vec: Vec<PeakAnnotation> = Vec::with_capacity(spec_map.len());
1029
1030 for ((scan, mz), (intensity, tof, inv_mobility, annotation, count)) in spec_map.iter() {
1031 scan_vec.push(*scan);
1032 mz_vec.push(*mz as f64 / 1_000_000.0);
1033 intensity_vec.push(*intensity);
1034 tof_vec.push((*tof as f64 / *count as f64) as u32);
1035 inv_mobility_vec.push(*inv_mobility / *count as f64);
1036 annotations_vec.push(annotation.clone());
1037 }
1038
1039 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());
1040
1041 TimsFrameAnnotated {
1042 frame_id: self.frame_id,
1043 retention_time: self.retention_time,
1044 ms_type: if self.ms_type == other.ms_type { self.ms_type.clone() } else { MsType::Unknown },
1045 tof: tof_vec,
1046 mz: mz_vec,
1047 scan: scan_vec,
1048 inv_mobility: inv_mobility_vec,
1049 intensity: intensity_vec,
1050 annotations: annotations_vec,
1051 }
1052 }
1053}
1054
1055#[cfg(test)]
1056mod new_sort_tests {
1057 use super::*;
1058
1059 fn ann(peptide_id: i32) -> PeakAnnotation {
1062 PeakAnnotation {
1063 contributions: vec![ContributionSource {
1064 intensity_contribution: 1.0,
1065 source_type: SourceType::Signal,
1066 signal_attributes: Some(SignalAttributes {
1067 charge_state: 1,
1068 peptide_id,
1069 isotope_peak: 0,
1070 fragment_kind: None,
1071 fragment_ordinal: None,
1072 }),
1073 }],
1074 }
1075 }
1076
1077 fn pids(spec: &MzSpectrumAnnotated) -> Vec<i32> {
1078 spec.annotations
1079 .iter()
1080 .map(|a| a.contributions[0].signal_attributes.as_ref().unwrap().peptide_id)
1081 .collect()
1082 }
1083
1084 #[test]
1088 fn new_sorts_by_mz_and_keeps_annotation_attached() {
1089 let spec = MzSpectrumAnnotated::new(
1091 vec![300.0, 100.0, 200.0],
1092 vec![3.0, 1.0, 2.0],
1093 vec![ann(3000), ann(1000), ann(2000)],
1094 );
1095
1096 assert_eq!(spec.mz, vec![100.0, 200.0, 300.0]);
1097 assert_eq!(spec.intensity, vec![1.0, 2.0, 3.0]);
1098 assert_eq!(pids(&spec), vec![1000, 2000, 3000]);
1099 }
1100
1101 #[test]
1104 fn new_is_stable_for_equal_mz() {
1105 let spec = MzSpectrumAnnotated::new(
1106 vec![150.0, 150.0],
1107 vec![10.0, 20.0],
1108 vec![ann(111), ann(222)],
1109 );
1110 assert_eq!(spec.intensity, vec![10.0, 20.0]);
1111 assert_eq!(pids(&spec), vec![111, 222]);
1112 }
1113}