#[derive(Debug)]
pub struct PolygonGate {
per_scan: Vec<Vec<(u32, u32)>>,
}
impl PolygonGate {
pub fn build(
mz: &[f64],
im: &[f64],
num_scans: usize,
im_at_scan: impl Fn(u32) -> f64,
mz_to_tof: impl Fn(f64) -> f64,
mz_pad: f64,
im_pad: f64,
) -> Option<Self> {
let n = mz.len();
if n < 3 || im.len() != n || num_scans == 0 {
return None;
}
let mut per_scan: Vec<Vec<(u32, u32)>> = vec![Vec::new(); num_scans];
for (s, slot) in per_scan.iter_mut().enumerate() {
let y0 = im_at_scan(s as u32);
let mut spans: Vec<(f64, f64)> = Vec::new();
for y in [y0 - im_pad, y0, y0 + im_pad] {
spans.extend(scanline_spans(mz, im, y));
if im_pad == 0.0 {
break;
}
}
if spans.is_empty() {
continue;
}
spans.sort_by(|a, b| a.0.total_cmp(&b.0));
let mut tof_iv: Vec<(u32, u32)> = Vec::new();
let mut cur = (spans[0].0 - mz_pad, spans[0].1 + mz_pad);
for &(lo, hi) in &spans[1..] {
let (lo, hi) = (lo - mz_pad, hi + mz_pad);
if lo <= cur.1 {
cur.1 = cur.1.max(hi);
} else {
push_tof_interval(&mut tof_iv, cur, &mz_to_tof);
cur = (lo, hi);
}
}
push_tof_interval(&mut tof_iv, cur, &mz_to_tof);
tof_iv.sort_unstable();
let mut merged: Vec<(u32, u32)> = Vec::with_capacity(tof_iv.len());
for (lo, hi) in tof_iv {
match merged.last_mut() {
Some(last) if lo <= last.1.saturating_add(1) => last.1 = last.1.max(hi),
_ => merged.push((lo, hi)),
}
}
*slot = merged;
}
if per_scan.iter().all(|row| row.is_empty()) {
return None;
}
Some(Self { per_scan })
}
pub fn contains(&self, scan: u32, tof: u32) -> bool {
let Some(row) = self.per_scan.get(scan as usize) else {
return false;
};
let i = row.partition_point(|&(_, hi)| hi < tof);
i < row.len() && tof >= row[i].0
}
pub fn keep_mask(&self, scan: &[u32], tof: &[u32]) -> Vec<bool> {
scan.iter()
.zip(tof)
.map(|(&s, &t)| self.contains(s, t))
.collect()
}
}
fn push_tof_interval(
out: &mut Vec<(u32, u32)>,
(lo, hi): (f64, f64),
mz_to_tof: &impl Fn(f64) -> f64,
) {
let t_lo = mz_to_tof(lo.max(0.0)).floor().max(0.0) as u32;
let t_hi = mz_to_tof(hi.max(0.0)).ceil().max(0.0) as u32;
if t_hi >= t_lo {
out.push((t_lo, t_hi));
}
}
fn scanline_spans(mz: &[f64], im: &[f64], y: f64) -> Vec<(f64, f64)> {
let n = mz.len();
let mut xs: Vec<f64> = Vec::new();
for i in 0..n {
let j = (i + 1) % n;
let (yi, yj) = (im[i], im[j]);
if (yi > y) != (yj > y) {
let t = (y - yi) / (yj - yi);
xs.push(mz[i] + t * (mz[j] - mz[i]));
}
}
xs.sort_by(f64::total_cmp);
xs.chunks_exact(2).map(|c| (c[0], c[1])).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn id_im(s: u32) -> f64 {
s as f64
}
fn id_tof(mz: f64) -> f64 {
mz
}
fn square() -> (Vec<f64>, Vec<f64>) {
(vec![10.0, 90.0, 90.0, 10.0], vec![10.0, 10.0, 90.0, 90.0])
}
#[test]
fn degenerate_polygon_yields_no_gate() {
assert!(
PolygonGate::build(&[0.0, 1.0], &[0.0, 1.0], 100, id_im, id_tof, 0.0, 0.0).is_none()
);
}
#[test]
fn square_keeps_inside_drops_outside() {
let (mz, im) = square();
let gate = PolygonGate::build(&mz, &im, 100, id_im, id_tof, 0.0, 0.0).unwrap();
assert!(gate.contains(50, 50)); assert!(gate.contains(10, 10)); assert!(gate.contains(89, 89)); assert!(!gate.contains(50, 9)); assert!(!gate.contains(50, 91)); assert!(!gate.contains(5, 50)); assert!(!gate.contains(95, 50)); }
#[test]
fn mz_pad_widens_the_kept_span() {
let (mz, im) = square();
let gate = PolygonGate::build(&mz, &im, 100, id_im, id_tof, 5.0, 0.0).unwrap();
assert!(gate.contains(50, 7)); assert!(gate.contains(50, 94)); assert!(!gate.contains(50, 4)); }
#[test]
fn im_pad_widens_the_mobility_band() {
let (mz, im) = square();
let gate = PolygonGate::build(&mz, &im, 100, id_im, id_tof, 0.0, 3.0).unwrap();
assert!(gate.contains(8, 50)); assert!(gate.contains(92, 50)); assert!(!gate.contains(5, 50)); }
#[test]
fn concave_polygon_gives_two_spans_on_a_scan() {
let mz = vec![0.0, 100.0, 100.0, 70.0, 70.0, 30.0, 30.0, 0.0];
let im = vec![0.0, 0.0, 100.0, 100.0, 30.0, 30.0, 100.0, 0.0];
let gate = PolygonGate::build(&mz, &im, 101, id_im, id_tof, 0.0, 0.0).unwrap();
assert!(gate.contains(50, 15)); assert!(gate.contains(50, 85)); assert!(!gate.contains(50, 50)); assert!(gate.contains(10, 50));
}
#[test]
fn keep_mask_matches_contains() {
let (mz, im) = square();
let gate = PolygonGate::build(&mz, &im, 100, id_im, id_tof, 0.0, 0.0).unwrap();
let scan = [50, 50, 5];
let tof = [50, 95, 50];
assert_eq!(gate.keep_mask(&scan, &tof), vec![true, false, false]);
}
}