use ndarray::Array2;
use thiserror::Error;
use crate::beam::Beam;
use crate::convolve_uv::{ConvolveError, convolve_uv};
#[derive(Debug, Error)]
pub enum SmoothError {
#[error("convolution failed: {0}")]
Convolve(#[from] ConvolveError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BrightnessUnit {
#[default]
JyPerBeam,
Kelvin,
}
impl BrightnessUnit {
pub fn parse(bunit: &str) -> Option<Self> {
let u = bunit.trim().trim_matches('\'').trim().to_ascii_uppercase();
match u.as_str() {
"K" | "KELVIN" => Some(BrightnessUnit::Kelvin),
"JY/BEAM" | "JY BEAM-1" | "JY/BM" | "JYBEAM" => Some(BrightnessUnit::JyPerBeam),
_ => None,
}
}
pub fn from_bunit(bunit: &str) -> Self {
match Self::parse(bunit) {
Some(unit) => unit,
None => {
tracing::warn!(
"Could not determine brightness unit from BUNIT={bunit:?}; \
assuming Jy/beam (flux scaling applied). Pass a recognised \
unit (e.g. 'Jy/beam' or 'K') to silence this warning."
);
BrightnessUnit::JyPerBeam
}
}
}
}
pub fn smooth(
image: &Array2<f32>,
old_beam: &Beam,
new_beam: &Beam,
dx_deg: f64,
dy_deg: f64,
cutoff_arcsec: Option<f64>,
unit: BrightnessUnit,
) -> Result<Array2<f32>, SmoothError> {
let result = convolve_uv(image, old_beam, new_beam, dx_deg, dy_deg, cutoff_arcsec)?;
let factor = match unit {
BrightnessUnit::JyPerBeam => result.scaling_factor,
BrightnessUnit::Kelvin => 1.0 / result.scaling_factor,
};
let scaled = result.image.mapv(|x| (factor as f32) * x);
Ok(scaled)
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array2;
#[test]
fn test_parse_recognised() {
assert_eq!(BrightnessUnit::parse("K"), Some(BrightnessUnit::Kelvin));
assert_eq!(BrightnessUnit::parse(" k "), Some(BrightnessUnit::Kelvin));
assert_eq!(BrightnessUnit::parse("'K'"), Some(BrightnessUnit::Kelvin));
assert_eq!(
BrightnessUnit::parse("Kelvin"),
Some(BrightnessUnit::Kelvin)
);
assert_eq!(
BrightnessUnit::parse("Jy/beam"),
Some(BrightnessUnit::JyPerBeam)
);
assert_eq!(
BrightnessUnit::parse("JY BEAM-1"),
Some(BrightnessUnit::JyPerBeam)
);
}
#[test]
fn test_parse_unrecognised_is_none() {
assert_eq!(BrightnessUnit::parse(""), None);
assert_eq!(BrightnessUnit::parse("Jy/pixel"), None);
assert_eq!(BrightnessUnit::parse("mJy"), None);
}
#[test]
fn test_from_bunit_falls_back_to_jy_per_beam() {
assert_eq!(BrightnessUnit::from_bunit("K"), BrightnessUnit::Kelvin);
assert_eq!(
BrightnessUnit::from_bunit("Jy/beam"),
BrightnessUnit::JyPerBeam
);
assert_eq!(BrightnessUnit::from_bunit("wat"), BrightnessUnit::JyPerBeam);
}
#[test]
fn test_kelvin_skips_flux_scaling() {
let old = Beam::new(10.0 / 3600.0, 10.0 / 3600.0, 0.0).unwrap();
let new = Beam::new(20.0 / 3600.0, 20.0 / 3600.0, 0.0).unwrap();
let img = Array2::from_elem((32, 32), 1.0_f32);
let dx = 2.5 / 3600.0;
let jy = smooth(&img, &old, &new, dx, dx, None, BrightnessUnit::JyPerBeam).unwrap();
let k = smooth(&img, &old, &new, dx, dx, None, BrightnessUnit::Kelvin).unwrap();
let center = (16, 16);
assert!(jy[center] > k[center] * 1.5, "Jy/beam should be scaled up");
assert!(
(k[center] - 1.0).abs() < 1e-3,
"Kelvin center = {}",
k[center]
);
}
}