use demosaic::{
demosaic, demosaic_interleaved, demosaic_quad_binned, interleaved_to_planar,
planar_to_interleaved, remosaic, Algorithm, BayerAlgorithm, CfaPattern, DemosaicError,
};
fn synthetic_input(width: usize, height: usize, cfa: &CfaPattern, rgb: [f32; 3]) -> Vec<f32> {
let mut input = vec![0.0f32; width * height];
for y in 0..height {
for x in 0..width {
input[y * width + x] = rgb[cfa.color_at(y, x) as usize];
}
}
input
}
fn assert_solid_reconstruction(
algorithm: Algorithm,
cfa: &CfaPattern,
width: usize,
height: usize,
tolerance: f32,
) {
let value = 0.5;
let input = synthetic_input(width, height, cfa, [value; 3]);
let mut output = vec![0.0f32; 3 * width * height];
demosaic(&input, width, height, cfa, algorithm, &mut output).unwrap();
let npix = width * height;
let border = 8;
for y in border..height.saturating_sub(border) {
for x in border..width.saturating_sub(border) {
let idx = y * width + x;
for c in 0..3 {
let v = output[c * npix + idx];
assert!(
(v - value).abs() < tolerance,
"{:?} on {:?} CFA: pixel ({y},{x}) channel {c} = {v}, expected ~{value} (tol {tolerance})",
algorithm,
if cfa.is_bayer() { "Bayer" } else if cfa.is_quad_bayer() { "Quad Bayer" } else { "X-Trans" },
);
}
}
}
}
#[test]
fn solid_bayer_bilinear() {
for cfa in &[
CfaPattern::bayer_rggb(),
CfaPattern::bayer_bggr(),
CfaPattern::bayer_grbg(),
CfaPattern::bayer_gbrg(),
] {
assert_solid_reconstruction(Algorithm::Bilinear, cfa, 64, 64, 1e-6);
}
}
#[test]
fn solid_bayer_mhc() {
assert_solid_reconstruction(Algorithm::Mhc, &CfaPattern::bayer_rggb(), 64, 64, 1e-4);
}
#[test]
fn solid_bayer_ppg() {
assert_solid_reconstruction(Algorithm::Ppg, &CfaPattern::bayer_rggb(), 64, 64, 1e-4);
}
#[test]
fn solid_bayer_ahd() {
assert_solid_reconstruction(Algorithm::Ahd, &CfaPattern::bayer_rggb(), 64, 64, 1e-4);
}
#[test]
fn solid_bayer_vng() {
assert_solid_reconstruction(Algorithm::Vng, &CfaPattern::bayer_rggb(), 64, 64, 1e-4);
}
#[test]
fn solid_xtrans_bilinear() {
assert_solid_reconstruction(
Algorithm::Bilinear,
&CfaPattern::xtrans_default(),
64,
64,
1e-6,
);
}
#[test]
fn solid_xtrans_markesteijn1() {
assert_solid_reconstruction(
Algorithm::Markesteijn1,
&CfaPattern::xtrans_default(),
128,
128,
1e-2,
);
}
#[test]
fn solid_xtrans_markesteijn3() {
assert_solid_reconstruction(
Algorithm::Markesteijn3,
&CfaPattern::xtrans_default(),
128,
128,
1e-2,
);
}
#[test]
fn solid_xtrans_dht() {
assert_solid_reconstruction(
Algorithm::Dht,
&CfaPattern::xtrans_default(),
128,
128,
1e-2,
);
}
#[test]
fn color_separation_bayer() {
let cfa = CfaPattern::bayer_rggb();
let (w, h) = (64, 64);
let rgb = [0.8, 0.5, 0.2];
let input = synthetic_input(w, h, &cfa, rgb);
let mut output = vec![0.0f32; 3 * w * h];
demosaic(&input, w, h, &cfa, Algorithm::Bilinear, &mut output).unwrap();
let npix = w * h;
let border = 4;
for y in border..h - border {
for x in border..w - border {
let idx = y * w + x;
for c in 0..3 {
let v = output[c * npix + idx];
assert!(
(v - rgb[c]).abs() < 1e-4,
"pixel ({y},{x}) channel {c} = {v}, expected {}",
rgb[c]
);
}
}
}
}
#[test]
fn error_input_size_mismatch() {
let cfa = CfaPattern::bayer_rggb();
let mut output = vec![0.0f32; 3 * 4 * 4];
let result = demosaic(&[0.0; 10], 4, 4, &cfa, Algorithm::Bilinear, &mut output);
assert!(matches!(
result,
Err(DemosaicError::InputSizeMismatch { expected: 16, got: 10 })
));
}
#[test]
fn error_output_size_mismatch() {
let cfa = CfaPattern::bayer_rggb();
let input = vec![0.0f32; 16];
let mut output = vec![0.0f32; 10];
let result = demosaic(&input, 4, 4, &cfa, Algorithm::Bilinear, &mut output);
assert!(matches!(
result,
Err(DemosaicError::OutputSizeMismatch { expected: 48, got: 10 })
));
}
#[test]
fn error_unsupported_algorithm_bayer() {
let cfa = CfaPattern::bayer_rggb();
let input = vec![0.0f32; 16];
let mut output = vec![0.0f32; 48];
let result = demosaic(&input, 4, 4, &cfa, Algorithm::Markesteijn1, &mut output);
assert!(matches!(
result,
Err(DemosaicError::UnsupportedAlgorithm { .. })
));
}
#[test]
fn error_unsupported_algorithm_xtrans() {
let cfa = CfaPattern::xtrans_default();
let (w, h) = (64, 64);
let input = vec![0.0f32; w * h];
let mut output = vec![0.0f32; 3 * w * h];
let result = demosaic(&input, w, h, &cfa, Algorithm::Ppg, &mut output);
assert!(matches!(
result,
Err(DemosaicError::UnsupportedAlgorithm { .. })
));
}
#[test]
fn error_xtrans_image_too_small() {
let cfa = CfaPattern::xtrans_default();
let (w, h) = (12, 12);
let input = vec![0.0f32; w * h];
let mut output = vec![0.0f32; 3 * w * h];
let result = demosaic(&input, w, h, &cfa, Algorithm::Markesteijn1, &mut output);
assert!(matches!(
result,
Err(DemosaicError::ImageTooSmall { .. })
));
}
#[test]
fn interleaved_matches_planar_bayer() {
let cfa = CfaPattern::bayer_rggb();
let (w, h) = (64, 64);
let npix = w * h;
let input = synthetic_input(w, h, &cfa, [0.8, 0.5, 0.2]);
let mut planar = vec![0.0f32; 3 * npix];
let mut interleaved = vec![0.0f32; 3 * npix];
demosaic(&input, w, h, &cfa, Algorithm::Bilinear, &mut planar).unwrap();
demosaic_interleaved(&input, w, h, &cfa, Algorithm::Bilinear, &mut interleaved).unwrap();
for i in 0..npix {
assert_eq!(interleaved[3 * i], planar[i], "R mismatch at pixel {i}");
assert_eq!(interleaved[3 * i + 1], planar[npix + i], "G mismatch at pixel {i}");
assert_eq!(interleaved[3 * i + 2], planar[2 * npix + i], "B mismatch at pixel {i}");
}
}
#[test]
fn interleaved_matches_planar_xtrans() {
let cfa = CfaPattern::xtrans_default();
let (w, h) = (128, 128);
let npix = w * h;
let input = synthetic_input(w, h, &cfa, [0.7, 0.5, 0.3]);
let mut planar = vec![0.0f32; 3 * npix];
let mut interleaved = vec![0.0f32; 3 * npix];
demosaic(&input, w, h, &cfa, Algorithm::Markesteijn1, &mut planar).unwrap();
demosaic_interleaved(&input, w, h, &cfa, Algorithm::Markesteijn1, &mut interleaved).unwrap();
for i in 0..npix {
assert_eq!(interleaved[3 * i], planar[i], "R mismatch at pixel {i}");
assert_eq!(interleaved[3 * i + 1], planar[npix + i], "G mismatch at pixel {i}");
assert_eq!(interleaved[3 * i + 2], planar[2 * npix + i], "B mismatch at pixel {i}");
}
}
#[test]
fn planar_interleaved_round_trip() {
let npix = 100;
let mut planar = vec![0.0f32; 3 * npix];
for i in 0..3 * npix {
planar[i] = i as f32;
}
let mut interleaved = vec![0.0f32; 3 * npix];
planar_to_interleaved(&planar, &mut interleaved);
for i in 0..npix {
assert_eq!(interleaved[3 * i], i as f32); assert_eq!(interleaved[3 * i + 1], (npix + i) as f32); assert_eq!(interleaved[3 * i + 2], (2 * npix + i) as f32); }
let mut back = vec![0.0f32; 3 * npix];
interleaved_to_planar(&interleaved, &mut back);
assert_eq!(planar, back);
}
fn quad_bayer_cfas() -> [CfaPattern; 4] {
[
CfaPattern::quad_bayer_rggb(),
CfaPattern::quad_bayer_bggr(),
CfaPattern::quad_bayer_grbg(),
CfaPattern::quad_bayer_gbrg(),
]
}
#[test]
fn solid_quad_bayer_bilinear() {
for cfa in &quad_bayer_cfas() {
assert_solid_reconstruction(Algorithm::Bilinear, cfa, 64, 64, 1e-6);
}
}
#[test]
fn solid_quad_bayer_qppg() {
for cfa in &quad_bayer_cfas() {
assert_solid_reconstruction(Algorithm::QuadPpg, cfa, 64, 64, 1e-4);
}
}
#[test]
fn color_separation_quad_bayer() {
let cfa = CfaPattern::quad_bayer_rggb();
let (w, h) = (64, 64);
let rgb = [0.8, 0.5, 0.2];
let input = synthetic_input(w, h, &cfa, rgb);
let mut output = vec![0.0f32; 3 * w * h];
demosaic(&input, w, h, &cfa, Algorithm::Bilinear, &mut output).unwrap();
let npix = w * h;
let border = 4;
for y in border..h - border {
for x in border..w - border {
let idx = y * w + x;
for c in 0..3 {
let v = output[c * npix + idx];
assert!(
(v - rgb[c]).abs() < 0.05,
"pixel ({y},{x}) channel {c} = {v}, expected {}",
rgb[c]
);
}
}
}
}
#[test]
fn solid_quad_bayer_binned() {
for cfa in &quad_bayer_cfas() {
let (w, h) = (64, 64);
let input = synthetic_input(w, h, cfa, [0.5; 3]);
let ow = w / 2;
let oh = h / 2;
let out_npix = ow * oh;
for alg in &[
BayerAlgorithm::Bilinear,
BayerAlgorithm::Mhc,
BayerAlgorithm::Ppg,
BayerAlgorithm::Ahd,
BayerAlgorithm::Vng,
] {
let mut output = vec![0.0f32; 3 * out_npix];
demosaic_quad_binned(&input, w, h, cfa, *alg, &mut output).unwrap();
let border = 4;
for y in border..oh - border {
for x in border..ow - border {
let idx = y * ow + x;
for c in 0..3 {
let v = output[c * out_npix + idx];
assert!(
(v - 0.5).abs() < 1e-4,
"{alg:?}: ch {c} at ({y},{x}) = {v}, expected ~0.5"
);
}
}
}
}
}
}
#[test]
fn quad_binned_dimensions() {
let cfa = CfaPattern::quad_bayer_rggb();
let (w, h) = (64, 64);
let input = vec![0.5f32; w * h];
let ow = w / 2;
let oh = h / 2;
let mut output = vec![0.0f32; 3 * ow * oh];
assert!(demosaic_quad_binned(&input, w, h, &cfa, BayerAlgorithm::Bilinear, &mut output).is_ok());
let mut wrong = vec![0.0f32; 3 * w * h];
assert!(matches!(
demosaic_quad_binned(&input, w, h, &cfa, BayerAlgorithm::Bilinear, &mut wrong),
Err(DemosaicError::OutputSizeMismatch { .. })
));
}
#[test]
fn remosaic_passthrough_preserved() {
for cfa in &quad_bayer_cfas() {
let (w, h) = (32, 32);
let input = synthetic_input(w, h, cfa, [0.8, 0.5, 0.3]);
let bayer_cfa = cfa.quad_to_bayer().unwrap();
let mut output = vec![0.0f32; w * h];
remosaic(&input, w, h, cfa, &mut output).unwrap();
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
if cfa.color_at(y, x) == bayer_cfa.color_at(y, x) {
assert_eq!(output[idx], input[idx],
"pass-through pixel ({y},{x}) changed");
}
}
}
}
}
#[test]
fn remosaic_then_demosaic() {
let cfa = CfaPattern::quad_bayer_rggb();
let (w, h) = (64, 64);
let rgb = [0.8, 0.5, 0.3];
let input = synthetic_input(w, h, &cfa, rgb);
let bayer_cfa = cfa.quad_to_bayer().unwrap();
let mut bayer_data = vec![0.0f32; w * h];
remosaic(&input, w, h, &cfa, &mut bayer_data).unwrap();
let mut output = vec![0.0f32; 3 * w * h];
demosaic(&bayer_data, w, h, &bayer_cfa, Algorithm::Ppg, &mut output).unwrap();
let npix = w * h;
let border = 8;
for y in border..h - border {
for x in border..w - border {
let idx = y * w + x;
for c in 0..3 {
let v = output[c * npix + idx];
assert!(
(v - rgb[c]).abs() < 0.1,
"pixel ({y},{x}) channel {c} = {v}, expected {}",
rgb[c]
);
}
}
}
}
#[test]
fn error_unsupported_algorithm_quad_bayer() {
let cfa = CfaPattern::quad_bayer_rggb();
let (w, h) = (64, 64);
let input = vec![0.0f32; w * h];
let mut output = vec![0.0f32; 3 * w * h];
for alg in &[Algorithm::Mhc, Algorithm::Ppg, Algorithm::Ahd] {
assert!(matches!(
demosaic(&input, w, h, &cfa, *alg, &mut output),
Err(DemosaicError::UnsupportedAlgorithm { .. })
));
}
for alg in &[Algorithm::Markesteijn1, Algorithm::Markesteijn3, Algorithm::Dht] {
assert!(matches!(
demosaic(&input, w, h, &cfa, *alg, &mut output),
Err(DemosaicError::UnsupportedAlgorithm { .. })
));
}
}
#[test]
fn interleaved_matches_planar_quad_bayer() {
let cfa = CfaPattern::quad_bayer_rggb();
let (w, h) = (64, 64);
let npix = w * h;
let input = synthetic_input(w, h, &cfa, [0.7, 0.5, 0.3]);
let mut planar = vec![0.0f32; 3 * npix];
let mut interleaved = vec![0.0f32; 3 * npix];
demosaic(&input, w, h, &cfa, Algorithm::Bilinear, &mut planar).unwrap();
demosaic_interleaved(&input, w, h, &cfa, Algorithm::Bilinear, &mut interleaved).unwrap();
for i in 0..npix {
assert_eq!(interleaved[3 * i], planar[i], "R mismatch at pixel {i}");
assert_eq!(interleaved[3 * i + 1], planar[npix + i], "G mismatch at pixel {i}");
assert_eq!(interleaved[3 * i + 2], planar[2 * npix + i], "B mismatch at pixel {i}");
}
}