Skip to main content

timsrust_centroid/spectrum_reader/
wide_spectrum_reader.rs

1use 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                // mz_values: mz_values.into_iter().map(|x| x.into()).collect(),
162                subpeaks
163                    .iter()
164                    .map(|p| TofIndex::try_from(p.tof).unwrap())
165                    .collect(),
166                isolation_window,
167            );
168            // let spectrum = timsrust_core::Spectrum {
169            //     // mz_values: mz_values.into_iter().map(|x| x.into()).collect(),
170            //     tof_indices: subpeaks
171            //         .iter()
172            //         .map(|p| TofIndex::try_from(p.tof).unwrap())
173            //         .collect(),
174            //     intensities: intensity_values
175            //         .into_iter()
176            //         .map(|x| x.into())
177            //         .collect(),
178            //     precursor: Some(precursor),
179            //     index: id,
180            //     isolation_window,
181            //     ..Default::default()
182            // };
183            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            // Fill with dummy/defaults for any other fields if needed
235            ..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    // #[test]
254    // fn test_peaks_to_spectra_empty() {
255    //     // Use dummy converters and frame
256    //     let peaks = vec![dummy_peak(10, 100, 1000)];
257    //     // let frame = unsafe { std::mem::zeroed() }; // Only for minimal test, not for real use!
258    //     let frame = Frame::default();
259    //     let im_converter = unsafe { std::mem::zeroed() };
260    //     let mz_converter = unsafe { std::mem::zeroed() };
261    //     let spec_id = AtomicUsize::new(0);
262    //     let spectra = peaks_to_spectra(
263    //         peaks,
264    //         &frame,
265    //         4,
266    //         im_converter,
267    //         mz_converter,
268    //         &spec_id,
269    //         2,
270    //     );
271    //     assert!(spectra.is_empty());
272    // }
273}