timsrust_centroid/spectrum_reader/
wide_spectrum_reader.rs1use crate::{Peak, PeakReader, TimsResult};
2use rayon::prelude::*;
3use std::sync::{Arc, atomic};
4use timsrust_core::{
5 FrameIndex, Im, InvertibleConverter, Rt, ScanIndex, TofIndex,
6};
7
8pub struct WideSpectrumReader<
9 IonReader,
10 InfoReader,
11 ImConverter: InvertibleConverter<ScanIndex, Im>,
12> {
13 peak_reader: PeakReader<IonReader, InfoReader>,
14 im_converter: Arc<ImConverter>,
15 spec_id: atomic::AtomicUsize,
16 min_spectrum_size: usize,
17}
18
19impl<
20 IonReader: timsrust_core::utils::reader::Reader<timsrust_core::FrameIons>
21 + Sync
22 + Send,
23 InfoReader: timsrust_core::utils::reader::Reader<timsrust_core::FrameInfo>
24 + timsrust_core::utils::reader::IndexedReader<timsrust_core::FrameInfo>
25 + Sync
26 + Send,
27 ImConverter: InvertibleConverter<ScanIndex, Im> + Sync + Send,
28> WideSpectrumReader<IonReader, InfoReader, ImConverter>
29{
30 pub(crate) fn new(
31 peak_reader: PeakReader<IonReader, InfoReader>,
32 min_spectrum_size: usize,
33 im_converter: Arc<ImConverter>,
34 ) -> TimsResult<Self> {
35 let spec_id = atomic::AtomicUsize::new(0);
36 let result = Self {
37 peak_reader,
38 im_converter,
39 spec_id,
40 min_spectrum_size,
41 };
42 Ok(result)
43 }
44
45 pub fn get_spectra_from_frame(
46 &self,
47 index: usize,
48 ) -> Vec<timsrust_core::Spectrum> {
49 #[allow(clippy::collapsible_if)]
50 if let Ok(frame) = self
51 .peak_reader
52 .frame_reader()
53 .get_partial_frame_without_ions(index)
54 {
55 if frame.info().ms_level() == timsrust_core::MSLevel::MS2 {
56 if let Ok(peaks) = self.peak_reader.get_peaks_from_frame(index)
57 {
58 if !peaks.is_empty() {
59 let spectra = peaks_to_spectra(
60 peaks,
61 &frame,
62 self.peak_reader.scan_fwhm(),
63 self.im_converter.as_ref(),
64 &self.spec_id,
65 self.min_spectrum_size,
66 );
67 return spectra;
68 }
69 }
70 }
71 }
72 vec![]
73 }
74
75 pub fn len(&self) -> usize {
76 self.spec_id.load(atomic::Ordering::Relaxed)
77 }
78
79 pub fn is_empty(&self) -> bool {
80 self.len() == 0
81 }
82
83 pub fn scan_fwhm(&self) -> usize {
84 self.peak_reader.scan_fwhm()
85 }
86
87 pub fn tof_fwhm(&self) -> usize {
88 self.peak_reader.tof_fwhm()
89 }
90
91 pub fn frame_count(&self) -> usize {
92 self.peak_reader.frame_count()
93 }
94
95 pub fn _par_iter(
96 &self,
97 ) -> impl ParallelIterator<Item = timsrust_core::Spectrum> + '_ {
98 (0..self.frame_count())
99 .into_par_iter()
100 .flat_map(|index| self.get_spectra_from_frame(index))
101 }
102}
103
104fn peaks_to_spectra(
105 mut peaks: Vec<Peak>,
106 frame: &timsrust_core::Frame,
107 scan_fwhm: usize,
108 im_converter: impl InvertibleConverter<ScanIndex, Im>,
109 spec_id: &atomic::AtomicUsize,
110 min_spectrum_size: usize,
111) -> Vec<timsrust_core::Spectrum> {
112 peaks.sort_by(|a, b| a.scan.cmp(&b.scan));
113 split_peaks(&peaks, scan_fwhm)
114 .filter_map(|(subpeaks, scan)| {
115 if subpeaks.len() < min_spectrum_size {
116 return None;
117 }
118 let frame_info = frame.info();
119 let index = frame_info
120 .quadrupole_settings()
121 .scan_starts
122 .iter()
123 .position(|&s| s > scan)
124 .unwrap_or(frame_info.quadrupole_settings().len())
125 .max(1)
126 - 1;
127 let isolation_mz =
128 frame_info.quadrupole_settings().isolation_windows[index]
129 .center();
130 let isolation_width =
131 frame_info.quadrupole_settings().isolation_windows[index]
132 .width();
133 let ce = frame_info.quadrupole_settings().isolation_windows[index]
134 .collision_energy();
135 let intensity_values = subpeaks
136 .iter()
137 .map(|p| p.apex_intensity as f32)
138 .collect::<Vec<_>>();
139 let id = spec_id.fetch_add(1, atomic::Ordering::Relaxed);
140 let scan = ScanIndex::try_from(scan as u32).unwrap();
141 let precursor = timsrust_core::Precursor::new(
142 isolation_mz,
143 im_converter.convert(scan),
144 Rt::from(frame_info.rt_in_seconds()),
145 scan,
146 None,
147 None,
148 id,
149 FrameIndex::try_from(frame_info.index() as u32).unwrap(),
150 );
151 let isolation_window =
152 timsrust_core::IsolationWindow::new_from_center(
153 isolation_mz,
154 isolation_width,
155 ce,
156 );
157 let spectrum = timsrust_core::Spectrum::new(
158 intensity_values.into_iter().map(|x| x.into()).collect(),
159 id,
160 Some(precursor),
161 subpeaks
163 .iter()
164 .map(|p| TofIndex::try_from(p.tof).unwrap())
165 .collect(),
166 isolation_window,
167 );
168 Some(spectrum)
184 })
185 .collect()
186}
187
188fn split_peaks(
189 peaks: &[Peak],
190 scan_fwhm: usize,
191) -> impl Iterator<Item = (Vec<Peak>, usize)> + '_ {
192 assert!(peaks.is_sorted_by(|a, b| a.scan <= b.scan));
193 let step = (scan_fwhm) / 2;
194 let width = scan_fwhm;
195 let subpeaks = peaks;
196 let mut results = Vec::new();
197 if !subpeaks.is_empty() {
198 let mut start = 0;
199 let mut left = 0;
200 let n = subpeaks.len();
201 loop {
202 let end = start + width;
203 while left < n && (subpeaks[left].scan as usize) < start {
204 left += 1;
205 }
206 if left >= n {
207 break;
208 }
209 let mut right = left;
210 while right < n && (subpeaks[right].scan as usize) < end {
211 right += 1;
212 }
213 if left < right {
214 let mut chunk = subpeaks[left..right].to_vec();
215 chunk.sort_by(|a, b| a.tof.cmp(&b.tof));
216 let avg_scan = start + step;
217 results.push((chunk, avg_scan));
218 }
219 start += step;
220 }
221 }
222 results.into_iter()
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 fn dummy_peak(scan: u32, tof: u32, intensity: u32) -> Peak {
230 Peak {
231 scan,
232 tof,
233 apex_intensity: intensity as u64,
234 ..Default::default()
236 }
237 }
238
239 #[test]
240 fn test_split_peaks_runs() {
241 let peaks = vec![
242 dummy_peak(100, 100, 1000),
243 dummy_peak(120, 110, 900),
244 dummy_peak(150, 120, 800),
245 dummy_peak(200, 130, 700),
246 ];
247 let result = split_peaks(&peaks, 2).collect::<Vec<_>>();
248 assert!(!result.is_empty());
249 let total = result.iter().map(|(v, _)| v.len()).sum::<usize>();
250 assert_eq!(total, 2 * peaks.len());
251 }
252
253 }