use ndarray::{Array2, CowArray, Ix2};
use crate::beam::Beam;
use crate::convolve_uv::{ConvolveError, FftFloat, FftPlans, convolve_with_gain};
#[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 {
Self::parse(bunit).unwrap_or_else(|| {
tracing::warn!("{}", Self::unrecognised_message(bunit));
BrightnessUnit::JyPerBeam
})
}
pub(crate) fn unrecognised_message(bunit: &str) -> String {
format!(
"Could not determine brightness unit from BUNIT={bunit:?}; assuming \
Jy/beam (flux scaling applied). Use a recognised unit (e.g. 'Jy/beam' \
or 'K') to silence this warning."
)
}
}
pub fn smooth<'a, T: FftFloat>(
image: impl Into<CowArray<'a, T, Ix2>>,
old_beam: &Beam,
new_beam: &Beam,
dx_deg: f64,
dy_deg: f64,
unit: BrightnessUnit,
plans: Option<&FftPlans<T>>,
) -> Result<Array2<T>, ConvolveError> {
let result = convolve_with_gain(
image.into(),
old_beam,
new_beam,
(dx_deg, dy_deg),
plans,
|ratio| match unit {
BrightnessUnit::JyPerBeam => ratio * ratio,
BrightnessUnit::Kelvin => 1.0,
},
)?;
Ok(result.image)
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array2;
#[test]
fn test_parse_recognised() {
for (s, want) in [
("K", BrightnessUnit::Kelvin),
(" k ", BrightnessUnit::Kelvin),
("'K'", BrightnessUnit::Kelvin),
("Kelvin", BrightnessUnit::Kelvin),
("Jy/beam", BrightnessUnit::JyPerBeam),
("JY BEAM-1", BrightnessUnit::JyPerBeam),
] {
assert_eq!(BrightnessUnit::parse(s), Some(want), "{s}");
}
}
#[test]
fn test_parse_unrecognised_is_none() {
for s in ["", "Jy/pixel", "mJy"] {
assert_eq!(BrightnessUnit::parse(s), 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("wat"), BrightnessUnit::JyPerBeam);
}
#[test]
fn test_kelvin_skips_flux_scaling() {
let old = Beam::from_arcsec(10.0, 10.0, 0.0).unwrap();
let new = Beam::from_arcsec(20.0, 20.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, BrightnessUnit::JyPerBeam, None).unwrap();
let k = smooth(&img, &old, &new, dx, dx, BrightnessUnit::Kelvin, None).unwrap();
assert!(
(jy[(16, 16)] - 4.0).abs() < 1e-3,
"Jy/beam {}",
jy[(16, 16)]
);
assert!((k[(16, 16)] - 1.0).abs() < 1e-3, "Kelvin {}", k[(16, 16)]);
}
}