use crate::result::{Error, Result};
use crate::SweepProcessor;
use nexrad_model::data::{GateStatus, SweepField};
pub struct CorrelationCoefficientFilter {
threshold: f32,
cc_field: SweepField,
}
impl CorrelationCoefficientFilter {
pub fn new(threshold: f32, cc_field: SweepField) -> Result<Self> {
if threshold <= 0.0 || threshold > 1.0 {
return Err(Error::InvalidParameter(
"CC threshold must be in (0, 1]".to_string(),
));
}
Ok(Self {
threshold,
cc_field,
})
}
}
impl SweepProcessor for CorrelationCoefficientFilter {
fn name(&self) -> &str {
"CorrelationCoefficientFilter"
}
fn process(&self, input: &SweepField) -> Result<SweepField> {
if input.azimuth_count() != self.cc_field.azimuth_count() {
return Err(Error::InvalidGeometry(format!(
"CC field azimuth count ({}) does not match input field ({})",
self.cc_field.azimuth_count(),
input.azimuth_count(),
)));
}
let shared_gates = input.gate_count().min(self.cc_field.gate_count());
let mut output = input.clone();
for az_idx in 0..input.azimuth_count() {
for gate_idx in 0..shared_gates {
let (_, input_status) = input.get(az_idx, gate_idx);
if input_status != GateStatus::Valid {
continue;
}
let (cc_val, cc_status) = self.cc_field.get(az_idx, gate_idx);
if cc_status != GateStatus::Valid || cc_val < self.threshold {
output.set(az_idx, gate_idx, 0.0, GateStatus::NoData);
}
}
}
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_fields(gate_count: usize) -> (SweepField, SweepField) {
let azimuths = vec![0.0, 1.0, 2.0];
let mut target = SweepField::new_empty(
"Reflectivity",
"dBZ",
0.5,
azimuths.clone(),
1.0,
2.0,
0.25,
gate_count,
);
let mut cc = SweepField::new_empty("CC", "", 0.5, azimuths, 1.0, 2.0, 0.25, gate_count);
for az in 0..3 {
for gate in 0..gate_count {
target.set(az, gate, 30.0, GateStatus::Valid);
cc.set(az, gate, 0.98, GateStatus::Valid);
}
}
(target, cc)
}
#[test]
fn test_cc_filter_preserves_high_cc() {
let (target, cc) = make_fields(5);
let filter = CorrelationCoefficientFilter::new(0.90, cc).unwrap();
let result = filter.process(&target).unwrap();
for az in 0..3 {
for gate in 0..5 {
let (val, status) = result.get(az, gate);
assert_eq!(val, 30.0);
assert_eq!(status, GateStatus::Valid);
}
}
}
#[test]
fn test_cc_filter_masks_low_cc() {
let (target, mut cc) = make_fields(5);
cc.set(1, 2, 0.5, GateStatus::Valid);
cc.set(1, 3, 0.8, GateStatus::Valid);
let filter = CorrelationCoefficientFilter::new(0.90, cc).unwrap();
let result = filter.process(&target).unwrap();
let (_, status) = result.get(1, 2);
assert_eq!(status, GateStatus::NoData);
let (_, status) = result.get(1, 3);
assert_eq!(status, GateStatus::NoData);
let (val, status) = result.get(1, 1);
assert_eq!(val, 30.0);
assert_eq!(status, GateStatus::Valid);
}
#[test]
fn test_cc_filter_masks_invalid_cc() {
let (target, mut cc) = make_fields(5);
cc.set(0, 0, 0.0, GateStatus::NoData);
let filter = CorrelationCoefficientFilter::new(0.90, cc).unwrap();
let result = filter.process(&target).unwrap();
let (_, status) = result.get(0, 0);
assert_eq!(status, GateStatus::NoData);
}
#[test]
fn test_cc_filter_invalid_threshold() {
let (_, cc) = make_fields(5);
assert!(CorrelationCoefficientFilter::new(0.0, cc).is_err());
let (_, cc) = make_fields(5);
assert!(CorrelationCoefficientFilter::new(1.5, cc).is_err());
}
#[test]
fn test_cc_filter_different_gate_counts_ok() {
let (target, _) = make_fields(10);
let (_, cc_shorter) = make_fields(5);
let filter = CorrelationCoefficientFilter::new(0.90, cc_shorter).unwrap();
let result = filter.process(&target).unwrap();
let (val, status) = result.get(0, 2);
assert_eq!(val, 30.0);
assert_eq!(status, GateStatus::Valid);
let (val, status) = result.get(0, 7);
assert_eq!(val, 30.0);
assert_eq!(status, GateStatus::Valid);
}
#[test]
fn test_cc_filter_azimuth_mismatch_error() {
let azimuths_3 = vec![0.0, 1.0, 2.0];
let azimuths_4 = vec![0.0, 1.0, 2.0, 3.0];
let mut target =
SweepField::new_empty("Reflectivity", "dBZ", 0.5, azimuths_3, 1.0, 2.0, 0.25, 5);
let mut cc = SweepField::new_empty("CC", "", 0.5, azimuths_4, 1.0, 2.0, 0.25, 5);
for az in 0..3 {
for gate in 0..5 {
target.set(az, gate, 30.0, GateStatus::Valid);
}
}
for az in 0..4 {
for gate in 0..5 {
cc.set(az, gate, 0.98, GateStatus::Valid);
}
}
let filter = CorrelationCoefficientFilter::new(0.90, cc).unwrap();
assert!(filter.process(&target).is_err());
}
}