#[derive(Debug, Clone, Copy)]
pub struct TofScanBox {
pub scan_lo: u32,
pub scan_hi: u32,
pub tof_lo: u32,
pub tof_hi: u32,
}
#[derive(Debug)]
pub struct DiaMs1Gate {
per_scan: Vec<Vec<(u32, u32)>>,
}
impl DiaMs1Gate {
pub fn build(boxes: &[TofScanBox], num_scans: usize) -> Option<Self> {
if boxes.is_empty() || num_scans == 0 {
return None;
}
let mut per_scan: Vec<Vec<(u32, u32)>> = vec![Vec::new(); num_scans];
for b in boxes {
if b.tof_hi < b.tof_lo {
continue;
}
let lo = b.scan_lo.min(num_scans as u32 - 1);
let hi = b.scan_hi.min(num_scans as u32 - 1);
for s in lo..=hi {
per_scan[s as usize].push((b.tof_lo, b.tof_hi));
}
}
for row in &mut per_scan {
row.sort_unstable();
let mut merged: Vec<(u32, u32)> = Vec::with_capacity(row.len());
for &(lo, hi) in row.iter() {
match merged.last_mut() {
Some(last) if lo <= last.1.saturating_add(1) => last.1 = last.1.max(hi),
_ => merged.push((lo, hi)),
}
}
*row = merged;
}
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()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn boxed(scan_lo: u32, scan_hi: u32, tof_lo: u32, tof_hi: u32) -> TofScanBox {
TofScanBox {
scan_lo,
scan_hi,
tof_lo,
tof_hi,
}
}
#[test]
fn empty_boxes_yields_no_gate() {
assert!(DiaMs1Gate::build(&[], 100).is_none());
}
#[test]
fn point_inside_window_is_kept_outside_is_dropped() {
let gate = DiaMs1Gate::build(&[boxed(10, 20, 1000, 2000)], 100).unwrap();
assert!(gate.contains(15, 1500)); assert!(gate.contains(10, 1000)); assert!(gate.contains(20, 2000));
assert!(!gate.contains(15, 999)); assert!(!gate.contains(15, 2001)); assert!(!gate.contains(9, 1500)); assert!(!gate.contains(21, 1500)); }
#[test]
fn padding_is_already_baked_into_the_box() {
let nominal_hi = 2000;
let padded = boxed(10, 20, 1000, nominal_hi + 500);
let gate = DiaMs1Gate::build(&[padded], 100).unwrap();
assert!(gate.contains(15, nominal_hi + 400)); assert!(!gate.contains(15, nominal_hi + 600)); }
#[test]
fn overlapping_windows_on_a_scan_merge() {
let gate = DiaMs1Gate::build(&[boxed(10, 20, 1000, 1500), boxed(12, 18, 1490, 2000)], 100)
.unwrap();
assert!(gate.contains(15, 1495));
assert!(gate.contains(15, 1000));
assert!(gate.contains(15, 2000));
assert!(!gate.contains(15, 2500));
}
#[test]
fn keep_mask_matches_per_point_contains() {
let gate = DiaMs1Gate::build(&[boxed(10, 20, 1000, 2000)], 100).unwrap();
let scan = [15, 15, 9];
let tof = [1500, 3000, 1500];
assert_eq!(gate.keep_mask(&scan, &tof), vec![true, false, false]);
}
}