use crate::params::CropParams;
#[derive(Debug, Clone, Copy)]
pub struct CropGate {
tof_lo: u32,
tof_hi: u32,
scan_lo: u32,
scan_hi: u32,
int_lo: u32,
int_hi: u32,
active: bool,
}
impl CropGate {
pub fn build(
p: &CropParams,
num_scans: usize,
mz_to_tof: impl Fn(f64) -> f64,
k0_to_scan: impl Fn(f64) -> f64,
) -> Self {
let clamp_u32 = |x: f64| x.max(0.0).min(u32::MAX as f64);
let tof_lo = p
.mz_min
.map(|mz| clamp_u32(mz_to_tof(mz).floor()) as u32)
.unwrap_or(0);
let tof_hi = p
.mz_max
.map(|mz| clamp_u32(mz_to_tof(mz).ceil()) as u32)
.unwrap_or(u32::MAX);
let scan_cap = num_scans.saturating_sub(1) as u32;
let scan_lo = p
.im_max
.map(|k0| clamp_u32(k0_to_scan(k0).floor()) as u32)
.unwrap_or(0);
let scan_hi = p
.im_min
.map(|k0| (clamp_u32(k0_to_scan(k0).ceil()) as u32).min(scan_cap))
.unwrap_or(u32::MAX);
let int_lo = p.min_intensity.unwrap_or(0);
let int_hi = p.max_intensity.unwrap_or(u32::MAX);
let active = tof_lo > 0
|| tof_hi < u32::MAX
|| scan_lo > 0
|| scan_hi < u32::MAX
|| int_lo > 0
|| int_hi < u32::MAX;
Self {
tof_lo,
tof_hi,
scan_lo,
scan_hi,
int_lo,
int_hi,
active,
}
}
pub fn is_active(&self) -> bool {
self.active
}
#[inline]
pub fn keep(&self, scan: u32, tof: u32, intensity: u32) -> bool {
scan >= self.scan_lo
&& scan <= self.scan_hi
&& tof >= self.tof_lo
&& tof <= self.tof_hi
&& intensity >= self.int_lo
&& intensity <= self.int_hi
}
pub fn apply(&self, scan: &[u32], tof: &[u32], intensity: &[u32], keep: &mut [bool]) {
if !self.active {
return;
}
for i in 0..keep.len() {
if keep[i] && !self.keep(scan[i], tof[i], intensity[i]) {
keep[i] = false;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn gate(p: &CropParams) -> CropGate {
CropGate::build(p, 1000, |mz| mz, |k0| k0)
}
#[test]
fn unset_crop_is_inactive_and_keeps_everything() {
let g = gate(&CropParams::default());
assert!(!g.is_active());
assert!(g.keep(0, 0, 0));
assert!(g.keep(999, u32::MAX, u32::MAX));
}
#[test]
fn mz_and_intensity_bounds_are_inclusive() {
let p = CropParams {
mz_min: Some(100.0),
mz_max: Some(200.0),
min_intensity: Some(10),
max_intensity: Some(50),
..CropParams::default()
};
let g = gate(&p);
assert!(g.is_active());
assert!(g.keep(0, 100, 10)); assert!(g.keep(0, 200, 50));
assert!(!g.keep(0, 99, 30)); assert!(!g.keep(0, 201, 30)); assert!(!g.keep(0, 150, 9)); assert!(!g.keep(0, 150, 51)); }
#[test]
fn mobility_max_maps_to_low_scan() {
let p = CropParams {
im_min: Some(100.0),
im_max: Some(300.0),
..CropParams::default()
};
let g = CropGate::build(&p, 1000, |mz| mz, |k0| 1000.0 - k0);
assert!(g.keep(700, 0, 1));
assert!(g.keep(900, 0, 1));
assert!(!g.keep(699, 0, 1));
assert!(!g.keep(901, 0, 1));
}
#[test]
fn apply_ands_into_existing_mask() {
let p = CropParams {
mz_min: Some(10.0),
..CropParams::default()
};
let g = gate(&p);
let scan = [0u32, 0, 0];
let tof = [5u32, 20, 30];
let inten = [1u32, 1, 1];
let mut keep = [true, true, false];
g.apply(&scan, &tof, &inten, &mut keep);
assert_eq!(keep, [false, true, false]); }
}