nexrad_process/filter/
threshold.rs1use crate::result::Result;
2use crate::SweepProcessor;
3use nexrad_model::data::{GateStatus, SweepField};
4
5pub struct ThresholdFilter {
20 pub min: Option<f32>,
22 pub max: Option<f32>,
24}
25
26impl SweepProcessor for ThresholdFilter {
27 fn name(&self) -> &str {
28 "ThresholdFilter"
29 }
30
31 fn process(&self, input: &SweepField) -> Result<SweepField> {
32 let mut output = input.clone();
33
34 for az_idx in 0..output.azimuth_count() {
35 for gate_idx in 0..output.gate_count() {
36 let (val, status) = output.get(az_idx, gate_idx);
37
38 if status == GateStatus::Valid {
39 let below_min = self.min.is_some_and(|m| val < m);
40 let above_max = self.max.is_some_and(|m| val > m);
41
42 if below_min || above_max {
43 output.set(az_idx, gate_idx, 0.0, GateStatus::NoData);
44 }
45 }
46 }
47 }
48
49 Ok(output)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::SweepProcessor;
57
58 fn make_test_field() -> SweepField {
59 let mut field =
60 SweepField::new_empty("Test", "dBZ", 0.5, vec![0.0, 1.0, 2.0], 1.0, 2.0, 0.25, 5);
61
62 for az in 0..3 {
64 for gate in 0..5 {
65 let value = (az * 5 + gate) as f32 * 5.0; field.set(az, gate, value, GateStatus::Valid);
67 }
68 }
69
70 field
71 }
72
73 #[test]
74 fn test_threshold_min_only() {
75 let field = make_test_field();
76 let filter = ThresholdFilter {
77 min: Some(20.0),
78 max: None,
79 };
80
81 let result = filter.process(&field).unwrap();
82
83 for az in 0..3 {
85 for gate in 0..5 {
86 let original_value = (az * 5 + gate) as f32 * 5.0;
87 let (val, status) = result.get(az, gate);
88 if original_value < 20.0 {
89 assert_eq!(status, GateStatus::NoData);
90 assert_eq!(val, 0.0);
91 } else {
92 assert_eq!(status, GateStatus::Valid);
93 assert_eq!(val, original_value);
94 }
95 }
96 }
97 }
98
99 #[test]
100 fn test_threshold_max_only() {
101 let field = make_test_field();
102 let filter = ThresholdFilter {
103 min: None,
104 max: Some(40.0),
105 };
106
107 let result = filter.process(&field).unwrap();
108
109 for az in 0..3 {
110 for gate in 0..5 {
111 let original_value = (az * 5 + gate) as f32 * 5.0;
112 let (_, status) = result.get(az, gate);
113 if original_value > 40.0 {
114 assert_eq!(status, GateStatus::NoData);
115 } else {
116 assert_eq!(status, GateStatus::Valid);
117 }
118 }
119 }
120 }
121
122 #[test]
123 fn test_threshold_both_bounds() {
124 let field = make_test_field();
125 let filter = ThresholdFilter {
126 min: Some(15.0),
127 max: Some(50.0),
128 };
129
130 let result = filter.process(&field).unwrap();
131
132 for az in 0..3 {
133 for gate in 0..5 {
134 let original_value = (az * 5 + gate) as f32 * 5.0;
135 let (_, status) = result.get(az, gate);
136 if original_value < 15.0 || original_value > 50.0 {
137 assert_eq!(status, GateStatus::NoData);
138 } else {
139 assert_eq!(status, GateStatus::Valid);
140 }
141 }
142 }
143 }
144
145 #[test]
146 fn test_threshold_preserves_nodata() {
147 let mut field = make_test_field();
148 field.set(1, 2, 0.0, GateStatus::NoData);
150
151 let filter = ThresholdFilter {
152 min: Some(0.0),
153 max: None,
154 };
155
156 let result = filter.process(&field).unwrap();
157
158 let (_, status) = result.get(1, 2);
160 assert_eq!(status, GateStatus::NoData);
161 }
162
163 #[test]
164 fn test_threshold_no_bounds() {
165 let field = make_test_field();
166 let filter = ThresholdFilter {
167 min: None,
168 max: None,
169 };
170
171 let result = filter.process(&field).unwrap();
172
173 for az in 0..3 {
175 for gate in 0..5 {
176 let (orig_val, _) = field.get(az, gate);
177 let (result_val, result_status) = result.get(az, gate);
178 assert_eq!(result_val, orig_val);
179 assert_eq!(result_status, GateStatus::Valid);
180 }
181 }
182 }
183}