use dnoise::FilterParams;
use dnoise::filter::{filter_iterated, filter_once};
use dnoise::frame::FlatFrame;
fn frame(num_scans: usize, points: &[(u32, u32, u32)]) -> FlatFrame {
FlatFrame {
frame_id: 1,
num_scans,
scan: points.iter().map(|&(s, _, _)| s).collect(),
tof: points.iter().map(|&(_, t, _)| t).collect(),
intensity: points.iter().map(|&(_, _, i)| i).collect(),
}
}
#[test]
fn keeps_long_vertical_streak_drops_isolated() {
let mut pts = Vec::new();
for s in 10..18u32 {
pts.push((s, 1000, 100));
}
pts.push((50, 2000, 100));
pts.push((60, 2000, 100));
let f = frame(700, &pts);
let params = FilterParams::default();
let keep = filter_once(&f, ¶ms);
assert!(
keep[..8].iter().all(|&k| k),
"all streak points should be kept"
);
assert!(!keep[8]);
assert!(!keep[9]);
}
#[test]
fn length_counts_occupied_scans_not_gap_inclusive_span() {
let pts: Vec<(u32, u32, u32)> = [10u32, 12, 14].iter().map(|&s| (s, 1000, 100)).collect();
let f = frame(700, &pts);
let params = FilterParams {
min_feature_length: 5,
max_internal_gap: 1,
..Default::default()
};
let keep = filter_once(&f, ¶ms);
assert!(
keep.iter().all(|&k| !k),
"3 occupied scans (< min_feature_length 5) must be dropped despite a span of 5"
);
let p3 = FilterParams {
min_feature_length: 3,
..params
};
assert!(filter_once(&f, &p3).iter().all(|&k| k));
}
#[test]
fn gap_closing_bridges_small_gaps() {
let pts: Vec<(u32, u32, u32)> = [10u32, 11, 12, 14, 15, 16]
.iter()
.map(|&s| (s, 1000, 100))
.collect();
let f = frame(700, &pts);
let params = FilterParams {
min_feature_length: 5,
max_internal_gap: 1,
..Default::default()
};
let keep = filter_once(&f, ¶ms);
assert!(
keep.iter().all(|&k| k),
"all points in the bridged run should survive"
);
}
#[test]
fn gap_too_large_splits_into_short_runs() {
let pts: Vec<(u32, u32, u32)> = [10u32, 11, 12, 16, 17, 18]
.iter()
.map(|&s| (s, 1000, 100))
.collect();
let f = frame(700, &pts);
let params = FilterParams {
min_feature_length: 5,
max_internal_gap: 1,
..Default::default()
};
let keep = filter_once(&f, ¶ms);
assert!(
keep.iter().all(|&k| !k),
"split short runs should all be dropped"
);
}
#[test]
fn window_aggregates_neighboring_tofs() {
let mut pts = Vec::new();
for s in 10..13u32 {
pts.push((s, 1000, 100));
}
for s in 13..16u32 {
pts.push((s, 1001, 100));
}
let f = frame(700, &pts);
let params = FilterParams {
mz_half_width: 2,
min_feature_length: 5,
..Default::default()
};
let keep = filter_once(&f, ¶ms);
assert!(
keep.iter().all(|&k| k),
"neighboring TOFs should aggregate into one feature"
);
}
#[test]
fn iteration_is_monotonic() {
let mut pts = Vec::new();
for s in 10..18u32 {
pts.push((s, 1000, 100));
}
pts.push((50, 2000, 100));
let f = frame(700, &pts);
let p1 = FilterParams {
num_iterations: 1,
..Default::default()
};
let p3 = FilterParams {
num_iterations: 3,
..Default::default()
};
let k1 = filter_iterated(&f, &p1);
let k3 = filter_iterated(&f, &p3);
let c1 = k1.iter().filter(|&&b| b).count();
let c3 = k3.iter().filter(|&&b| b).count();
assert!(c3 <= c1, "more iterations must not keep more points");
assert!(k3[0..8].iter().all(|&b| b));
}