use crate::error::{DnoiseError, Result};
use crate::filter::filter_iterated;
use crate::frame::FlatFrame;
use crate::halo::horizontal_halo_keep_mask;
use crate::params::{FilterParams, HaloParams, MsmsFilterParams};
use crate::tdf::{FrameMeta, PasefWindow};
use rayon::prelude::*;
use std::collections::{HashMap, HashSet};
use timsrust::readers::FrameReader;
#[inline]
fn key(scan: u32, tof: u32) -> u64 {
((scan as u64) << 32) | tof as u64
}
pub fn combine_and_filter(
points: &[(u32, u32, u32)],
scan_begin: u32,
num_local_scans: usize,
params: &FilterParams,
halo: Option<&HaloParams>,
) -> HashSet<u64> {
let mut out = HashSet::new();
if points.is_empty() || num_local_scans == 0 {
return out;
}
let mut acc: HashMap<u64, u64> = HashMap::new();
for &(s, t, i) in points {
let sl = s.saturating_sub(scan_begin);
*acc.entry(key(sl, t)).or_insert(0) += i as u64;
}
let n = acc.len();
let mut scan = Vec::with_capacity(n);
let mut tof = Vec::with_capacity(n);
let mut intensity = Vec::with_capacity(n);
for (&k, &sum) in &acc {
scan.push((k >> 32) as u32);
tof.push((k & 0xFFFF_FFFF) as u32);
intensity.push(sum.min(u32::MAX as u64) as u32);
}
let frame = FlatFrame {
frame_id: 0,
num_scans: num_local_scans,
scan,
tof,
intensity,
};
let mut keep = filter_iterated(&frame, params);
if let Some(hp) = halo {
let idx: Vec<usize> = (0..frame.len()).filter(|&i| keep[i]).collect();
if !idx.is_empty() {
let s: Vec<u32> = idx.iter().map(|&i| frame.scan[i]).collect();
let t: Vec<u32> = idx.iter().map(|&i| frame.tof[i]).collect();
let it: Vec<u32> = idx.iter().map(|&i| frame.intensity[i]).collect();
let hmask = horizontal_halo_keep_mask(&s, &t, &it, num_local_scans, hp);
for (k, &i) in idx.iter().enumerate() {
if !hmask[k] {
keep[i] = false;
}
}
}
}
for (i, &k) in keep.iter().enumerate() {
if k {
out.insert(key(frame.scan[i] + scan_begin, frame.tof[i]));
}
}
out
}
pub(crate) struct MsmsKeep {
keep: Vec<HashSet<u64>>,
frame_windows: HashMap<usize, Vec<(u32, u32, u32)>>,
}
impl MsmsKeep {
pub(crate) fn keep_mask(&self, frame: &FlatFrame, frame_id: usize) -> Vec<bool> {
let windows = match self.frame_windows.get(&frame_id) {
Some(w) => w,
None => return vec![true; frame.len()],
};
let mut keep = vec![true; frame.len()];
for (i, slot) in keep.iter_mut().enumerate() {
let s = frame.scan[i];
if let Some(&(_, _, prec)) = windows.iter().find(|&&(sb, se, _)| s >= sb && s < se) {
*slot = self.keep[prec as usize].contains(&key(s, frame.tof[i]));
}
}
keep
}
}
pub(crate) fn build_msms_keep(
reader: &FrameReader,
meta: &[FrameMeta],
windows: &[PasefWindow],
params: &MsmsFilterParams,
halo: Option<&HaloParams>,
) -> Result<MsmsKeep> {
let max_prec = windows.iter().map(|w| w.precursor).max().unwrap_or(0) as usize;
let mut sb0 = vec![u32::MAX; max_prec + 1];
let mut se0 = vec![0u32; max_prec + 1];
let mut frame_windows: HashMap<usize, Vec<(u32, u32, u32)>> = HashMap::new();
for w in windows {
let p = w.precursor as usize;
sb0[p] = sb0[p].min(w.scan_begin);
se0[p] = se0[p].max(w.scan_end);
frame_windows
.entry(w.frame)
.or_default()
.push((w.scan_begin, w.scan_end, w.precursor));
}
for v in frame_windows.values_mut() {
v.sort_by_key(|&(sb, _, _)| sb);
}
let mut raw: Vec<Vec<(u32, u32, u32)>> = vec![Vec::new(); max_prec + 1];
for (i, m) in meta.iter().enumerate() {
if m.num_peaks == 0 {
continue;
}
let ws = match frame_windows.get(&m.id) {
Some(w) => w,
None => continue,
};
let frame = reader.get(i).map_err(|e| DnoiseError::FrameRead {
index: i,
message: e.to_string(),
})?;
let flat = FlatFrame::from_frame(&frame);
for k in 0..flat.len() {
let s = flat.scan[k];
if let Some(&(_, _, prec)) = ws.iter().find(|&&(sb, se, _)| s >= sb && s < se) {
raw[prec as usize].push((s, flat.tof[k], flat.intensity[k]));
}
}
}
let fp = params.as_filter_params();
let keep: Vec<HashSet<u64>> = raw
.into_par_iter()
.enumerate()
.map(|(p, pts)| {
if p == 0 || pts.is_empty() {
HashSet::new()
} else {
let num_local = se0[p].saturating_sub(sb0[p]) as usize;
combine_and_filter(&pts, sb0[p], num_local, &fp, halo)
}
})
.collect();
Ok(MsmsKeep {
keep,
frame_windows,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn vertical_params() -> FilterParams {
FilterParams {
mz_half_width: 0,
min_feature_length: 3,
max_internal_gap: 0,
min_window_intensity: 0,
min_feature_intensity: 0,
num_iterations: 1,
}
}
#[test]
fn combine_and_filter_empty_input_is_empty() {
assert!(combine_and_filter(&[], 0, 10, &vertical_params(), None).is_empty());
}
#[test]
fn combine_and_filter_zero_local_scans_is_empty() {
let pts = [(5, 100, 10)];
assert!(combine_and_filter(&pts, 5, 0, &vertical_params(), None).is_empty());
}
#[test]
fn combine_and_filter_keeps_a_vertical_streak_in_absolute_coords() {
let pts: Vec<(u32, u32, u32)> = (10..20).map(|s| (s, 500, 100)).collect();
let out = combine_and_filter(&pts, 10, 10, &vertical_params(), None);
assert!(!out.is_empty());
assert!(out.contains(&(((10u64) << 32) | 500)));
assert!(out.contains(&(((19u64) << 32) | 500)));
}
#[test]
fn combine_and_filter_drops_an_isolated_point() {
let pts = [(12, 500, 100)];
assert!(combine_and_filter(&pts, 10, 10, &vertical_params(), None).is_empty());
}
#[test]
fn keep_mask_passes_all_points_when_frame_has_no_windows() {
let mk = MsmsKeep {
keep: vec![HashSet::new()],
frame_windows: HashMap::new(),
};
let frame = FlatFrame {
frame_id: 3,
num_scans: 5,
scan: vec![0, 1, 2],
tof: vec![10, 20, 30],
intensity: vec![1, 1, 1],
};
assert_eq!(mk.keep_mask(&frame, 3), vec![true, true, true]);
}
#[test]
fn keep_mask_prunes_in_window_points_by_precursor_keep_set() {
let mut frame_windows = HashMap::new();
frame_windows.insert(3usize, vec![(0u32, 2u32, 1u32)]);
let mut prec1 = HashSet::new();
prec1.insert(key(0, 10));
let mk = MsmsKeep {
keep: vec![HashSet::new(), prec1],
frame_windows,
};
let frame = FlatFrame {
frame_id: 3,
num_scans: 5,
scan: vec![0, 1, 3],
tof: vec![10, 20, 30],
intensity: vec![1, 1, 1],
};
assert_eq!(mk.keep_mask(&frame, 3), vec![true, false, true]);
}
}