use chess_corners_core::{radon_response_u8, ImageView, RadonBuffers, ResponseMap};
use crate::config::DetectorConfig;
use crate::error::ChessError;
use crate::upscale::{self, UpscaleBuffers};
pub fn radon_heatmap_u8(
img: &[u8],
width: u32,
height: u32,
cfg: &DetectorConfig,
) -> Result<ResponseMap, ChessError> {
cfg.upscale.validate()?;
let src_w = width as usize;
let src_h = height as usize;
let expected = src_w * src_h;
if img.len() != expected {
return Err(ChessError::DimensionMismatch {
expected,
actual: img.len(),
});
}
let view = ImageView::from_u8_slice(src_w, src_h, img).expect("dimensions were checked above");
let factor = cfg.upscale.effective_factor();
let radon_params = cfg.radon_detector_params();
let mut rb = RadonBuffers::new();
if factor <= 1 {
let resp = radon_response_u8(view.data, view.width, view.height, &radon_params, &mut rb);
return Ok(resp.to_response_map());
}
let mut up_buffers = UpscaleBuffers::new();
let upscaled = upscale::upscale_bilinear_u8(img, src_w, src_h, factor, &mut up_buffers)?;
let resp = radon_response_u8(
upscaled.data,
upscaled.width,
upscaled.height,
&radon_params,
&mut rb,
);
Ok(resp.to_response_map())
}
#[cfg(feature = "image")]
pub fn radon_heatmap_image(
img: &::image::GrayImage,
cfg: &DetectorConfig,
) -> Result<ResponseMap, ChessError> {
let (w, h) = img.dimensions();
radon_heatmap_u8(img.as_raw(), w, h, cfg)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DetectorConfig;
use chess_corners_core::{radon_response_u8 as core_radon, RadonBuffers as CoreRadonBuffers};
fn synthetic_board(w: usize, h: usize) -> Vec<u8> {
let cell = (w.min(h) / 9).max(2);
let mut out = vec![0u8; w * h];
for y in 0..h {
for x in 0..w {
let cx = x / cell;
let cy = y / cell;
out[y * w + x] = if (cx + cy) & 1 == 0 { 220 } else { 35 };
}
}
out
}
#[test]
fn heatmap_matches_core_path_no_upscale() {
let (w, h) = (96usize, 72usize);
let img = synthetic_board(w, h);
let cfg = DetectorConfig::radon();
let map = radon_heatmap_u8(&img, w as u32, h as u32, &cfg).unwrap();
let radon_params = cfg.radon_detector_params();
let mut rb = CoreRadonBuffers::new();
let view = core_radon(&img, w, h, &radon_params, &mut rb);
assert_eq!(map.width(), view.width());
assert_eq!(map.height(), view.height());
assert_eq!(map.data().len(), view.data().len());
assert_eq!(map.data(), view.data());
}
#[test]
fn heatmap_dimensions_match_working_resolution() {
let (w, h) = (96usize, 72usize);
let img = synthetic_board(w, h);
let cfg = DetectorConfig::radon();
let upsample = cfg.radon_detector_params().image_upsample.clamp(1, 2) as usize;
let map = radon_heatmap_u8(&img, w as u32, h as u32, &cfg).unwrap();
assert_eq!(map.width(), w * upsample);
assert_eq!(map.height(), h * upsample);
}
#[test]
fn heatmap_is_non_zero_on_a_board() {
let (w, h) = (96usize, 72usize);
let img = synthetic_board(w, h);
let cfg = DetectorConfig::radon();
let map = radon_heatmap_u8(&img, w as u32, h as u32, &cfg).unwrap();
let max = map.data().iter().copied().fold(f32::NEG_INFINITY, f32::max);
assert!(max > 0.0, "expected positive Radon response on a board");
}
#[test]
fn heatmap_honors_upscale_factor() {
use crate::upscale::UpscaleConfig;
let (w, h) = (48usize, 36usize);
let img = synthetic_board(w, h);
let mut cfg = DetectorConfig::radon();
cfg.upscale = UpscaleConfig::fixed(2);
let radon_upsample = cfg.radon_detector_params().image_upsample.clamp(1, 2) as usize;
let map = radon_heatmap_u8(&img, w as u32, h as u32, &cfg).unwrap();
assert_eq!(map.width(), w * 2 * radon_upsample);
assert_eq!(map.height(), h * 2 * radon_upsample);
}
}