#[cfg(test)]
mod jpeg2000_byte_alignment_validation {
use crate::formats::jpeg2000::read;
use std::path::Path;
const RGB_8X8_FIXTURE: &[u8] = include_bytes!(
"../../tests/fixtures/rgb_8x8_lossless.jp2"
);
const SENTINEL_STYLE_16X16_4B_FIXTURE: &[u8] = include_bytes!(
"../../tests/fixtures/sentinel_style_16x16_4band_lossless.jp2"
);
const TILED_RGB_64X64_BLOCK32_FIXTURE: &[u8] = include_bytes!(
"../../tests/fixtures/tiled_rgb_64x64_block32_lossless.jp2"
);
#[test]
fn a5_external_rgb_fixture_metadata_parsed_correctly() {
let jp2 = crate::formats::jpeg2000_core::reader::GeoJp2::from_bytes(RGB_8X8_FIXTURE)
.expect("A5: should parse external rgb_8x8_lossless.jp2");
assert_eq!(jp2.width(), 8, "A5: width");
assert_eq!(jp2.height(), 8, "A5: height");
assert_eq!(jp2.component_count(), 3, "A5: component count");
assert_eq!(jp2.bits_per_sample(), 16, "A5: bits per sample");
assert!(!jp2.is_signed(), "A5: image should be unsigned");
}
#[test]
fn a5_external_rgb_fixture_band0_all_100() {
let fixture_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/rgb_8x8_lossless.jp2");
let ras = read(fixture_path).expect("A5: adapter read should decode fixture");
assert_eq!(ras.bands, 3, "A5: expected 3 bands");
assert_eq!(ras.rows, 8, "A5: expected 8 rows");
assert_eq!(ras.cols, 8, "A5: expected 8 cols");
for row in 0isize..8isize {
for col in 0isize..8isize {
let v = ras.get(0, row, col) as u16;
assert_eq!(v, 100, "A5: band 0 mismatch at ({row},{col})");
}
}
}
#[test]
fn a5_external_rgb_fixture_band1_all_200() {
let fixture_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/rgb_8x8_lossless.jp2");
let ras = read(fixture_path).expect("A5: adapter read should decode fixture");
for row in 0isize..8isize {
for col in 0isize..8isize {
let v = ras.get(1, row, col) as u16;
assert_eq!(v, 200, "A5: band 1 mismatch at ({row},{col})");
}
}
}
#[test]
fn a5_external_rgb_fixture_band2_ramp() {
let fixture_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/rgb_8x8_lossless.jp2");
let ras = read(fixture_path).expect("A5: adapter read should decode fixture");
for row in 0isize..8isize {
for col in 0isize..8isize {
let expected = (row * 8 + col) as u16;
let got = ras.get(2, row, col) as u16;
assert_eq!(got, expected, "A5: band 2 mismatch at ({row},{col})");
}
}
}
#[test]
fn a5_sentinel_style_fixture_metadata_and_probes() {
let jp2 = crate::formats::jpeg2000_core::reader::GeoJp2::from_bytes(SENTINEL_STYLE_16X16_4B_FIXTURE)
.expect("A5: should parse sentinel-style fixture");
assert_eq!(jp2.width(), 16, "A5 sentinel: width");
assert_eq!(jp2.height(), 16, "A5 sentinel: height");
assert_eq!(jp2.component_count(), 4, "A5 sentinel: component count");
assert_eq!(jp2.bits_per_sample(), 16, "A5 sentinel: bits per sample");
let fixture_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/sentinel_style_16x16_4band_lossless.jp2");
let ras = read(fixture_path).expect("A5 sentinel: adapter read should decode fixture");
assert_eq!(ras.bands, 4, "A5 sentinel: raster bands");
assert_eq!(ras.rows, 16, "A5 sentinel: raster rows");
assert_eq!(ras.cols, 16, "A5 sentinel: raster cols");
let probes = [
(0isize, 0isize, 0isize, 1000u16),
(0isize, 15isize, 15isize, 1255u16),
(3isize, 0isize, 0isize, 4000u16),
(3isize, 10isize, 5isize, 4165u16),
(3isize, 0isize, 0isize, 4000u16),
(3isize, 15isize, 15isize, 4255u16),
];
for (band, row, col, expected) in probes {
let got = ras.get(band, row, col) as u16;
assert_eq!(got, expected, "A5 sentinel probe mismatch at band={band}, row={row}, col={col}");
}
}
#[test]
fn a4_sentinel_style_adapter_formula_and_ranges() {
let fixture_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/sentinel_style_16x16_4band_lossless.jp2");
let ras = read(fixture_path).expect("A4 sentinel: adapter read should decode fixture");
assert_eq!(ras.bands, 4, "A4 sentinel: raster bands");
assert_eq!(ras.rows, 16, "A4 sentinel: raster rows");
assert_eq!(ras.cols, 16, "A4 sentinel: raster cols");
let mut band_min = [u16::MAX; 4];
let mut band_max = [0u16; 4];
for band in 0isize..4isize {
for row in 0isize..16isize {
for col in 0isize..16isize {
let expected = ((band as u16 + 1) * 1000) + (row as u16 * 16) + col as u16;
let got = ras.get(band, row, col) as u16;
assert_eq!(got, expected, "A4 sentinel formula mismatch at band={band}, row={row}, col={col}");
let b = band as usize;
band_min[b] = band_min[b].min(got);
band_max[b] = band_max[b].max(got);
}
}
}
assert_eq!(band_min[0], 1000, "A4 sentinel: band 0 min mismatch");
assert_eq!(band_max[0], 1255, "A4 sentinel: band 0 max mismatch");
assert_eq!(band_min[1], 2000, "A4 sentinel: band 1 min mismatch");
assert_eq!(band_max[1], 2255, "A4 sentinel: band 1 max mismatch");
assert_eq!(band_min[2], 3000, "A4 sentinel: band 2 min mismatch");
assert_eq!(band_max[2], 3255, "A4 sentinel: band 2 max mismatch");
assert_eq!(band_min[3], 4000, "A4 sentinel: band 3 min mismatch");
assert_eq!(band_max[3], 4255, "A4 sentinel: band 3 max mismatch");
}
#[test]
fn a5_tiled_multicomponent_fixture_metadata_and_probes() {
let jp2 = crate::formats::jpeg2000_core::reader::GeoJp2::from_bytes(TILED_RGB_64X64_BLOCK32_FIXTURE)
.expect("A5: should parse tiled fixture");
assert_eq!(jp2.width(), 64, "A5 tiled: width");
assert_eq!(jp2.height(), 64, "A5 tiled: height");
assert_eq!(jp2.component_count(), 3, "A5 tiled: component count");
assert_eq!(jp2.bits_per_sample(), 16, "A5 tiled: bits per sample");
let fixture_path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/tiled_rgb_64x64_block32_lossless.jp2");
let ras = read(fixture_path).expect("A5 tiled: adapter read should decode fixture");
assert_eq!(ras.bands, 3, "A5 tiled: raster bands");
assert_eq!(ras.rows, 64, "A5 tiled: raster rows");
assert_eq!(ras.cols, 64, "A5 tiled: raster cols");
let probes = [
(0isize, 0isize, 0isize, 100u16),
(0isize, 63isize, 63isize, 163u16),
(1isize, 0isize, 0isize, 200u16),
(1isize, 63isize, 63isize, 263u16),
(2isize, 0isize, 0isize, 0u16),
(2isize, 63isize, 63isize, 4095u16),
(2isize, 17isize, 9isize, 1097u16),
];
for (band, row, col, expected) in probes {
let got = ras.get(band, row, col) as u16;
assert_eq!(got, expected, "A5 tiled probe mismatch at band={band}, row={row}, col={col}");
}
}
#[test]
fn test_b03_pixel_values_match_reference() {
use crate::raster::RasterData;
let b03_path = "/Users/johnlindsay/Documents/teaching/GEOG3420/W26/Labs/data/S2A_MSIL2A_20240727T161831_N0511_R040_T17TNJ_20240727T235645.SAFE/GRANULE/L2A_T17TNJ_A047513_20240727T161942/IMG_DATA/R10m/T17TNJ_20240727T161831_B03_10m.jp2";
if !Path::new(b03_path).exists() {
eprintln!("SKIP: B03 file not found at {}", b03_path);
return;
}
let reference_pixels: Vec<u16> = vec![
3032, 3056, 3258, 3216, 3070, 3140, 3220, 3376, 3448, 3500, 3460, 3524, 3536, 3588, 3708, 3520,
3170, 3112, 2974, 3094, 3144, 3132, 3022, 2876, 2990, 3034, 3012, 3140, 3066, 3036, 3184, 3238,
3148, 3360, 3360, 3412, 3758, 3788, 3654, 3612, 3418, 3462, 3454, 3340, 3162, 3212, 3236, 3204,
3340, 3478, 3352, 3468, 3676, 3610, 3294, 3230, 3392, 3248, 3396, 3540, 3508, 3456, 3448, 3476,
3612, 3476, 3472, 3204, 3230, 3464, 3528, 4092, 5428, 5988, 5604, 4852, 4040, 3462, 3296, 3272,
3264, 3272, 3268, 3226, 3364, 3722, 3500, 3506, 3368, 3322, 3354, 3412, 3328, 3204, 3210, 3230,
3278, 3240, 3216, 3242, 3266, 3240, 3240, 3322, 3282, 3332, 3584, 3472, 3500, 3452, 3104, 3136,
3278, 3230, 3186, 3258, 3038, 3072, 3172, 3140, 3180, 3230, 3296, 3286, 3408, 3418, 3618, 4204,
4088, 4424, 4472, 4428, 4244, 3644, 3330, 3212, 3196, 3136, 3152, 3308, 3330, 3158, 3166, 3616,
4060, 4136, 3972, 4732, 4816, 3432, 3374, 3296, 3268, 3228, 3324, 3224, 3160, 3264, 3164, 3436,
3380, 3224, 3196, 3128, 3180, 3128, 3172, 3184, 3124, 3116, 3086, 3056, 2968, 2860, 2876, 2896,
3072, 3136, 3156, 3162, 3078, 2988, 3180, 3232, 3196, 3138, 3196, 3094, 3130, 3284, 3236, 3288,
3256, 3204, 3178, 3094, 3190, 3160, 3118, 3180, 4324, 4576, 3816, 4208, 3894, 3826, 5064, 4836,
3782, 3268, 3078, 3284, 3368, 3480, 3476, 3410, 3418, 3430, 3552, 3500, 4400, 3512, 3376, 3354,
3318, 3344, 3356, 3392, 3376, 3316, 3206, 3168, 3166, 3088, 3092, 3070, 3022, 3056, 3002, 3148,
3080, 3080, 3064, 2992, 3008, 3012, 3028, 3000, 2938, 2940, 3074, 3058, 3024, 3100, 3102, 3034,
];
match read(b03_path) {
Ok(raster) => {
let width = raster.cols;
let height = raster.rows;
eprintln!("[b03_validation] Decoded: {}x{}", width, height);
let native_pixels = match &raster.data {
RasterData::U16(vec) => &vec[0..256.min(vec.len())],
RasterData::I16(_vec) => {
eprintln!("ERROR: Got I16 data, expected U16");
return;
},
_other => {
eprintln!("ERROR: Got wrong data type, expected U16");
return;
}
};
if native_pixels.len() < 256 {
eprintln!("WARNING: Only {} pixels available, expected 256", native_pixels.len());
}
let mut mismatches = 0;
let mut max_error = 0i32;
for (i, (&native, &reference)) in native_pixels.iter().zip(reference_pixels.iter()).enumerate() {
let error = (native as i32 - reference as i32).abs();
if error > 50 { if mismatches < 10 {
eprintln!("[b03_validation] Pixel[{}]: native={} reference={} error={}", i, native, reference, error);
}
mismatches += 1;
max_error = max_error.max(error);
}
}
eprintln!("[b03_validation] Total mismatches (>50 error): {}/{}", mismatches, native_pixels.len());
eprintln!("[b03_validation] Max error: {}", max_error);
if mismatches > 0 {
eprintln!("[b03_validation] MISMATCH DETECTED - decoder output differs from PIL reference");
} else {
eprintln!("[b03_validation] PASS - pixels match PIL reference!");
}
}
Err(e) => {
eprintln!("ERROR decoding B03: {}", e);
}
}
}
#[test]
fn test_ll_codeblock_0_0_isolation() {
let b03_path = "/Users/johnlindsay/Documents/teaching/GEOG3420/W26/Labs/data/S2A_MSIL2A_20240727T161831_N0511_R040_T17TNJ_20240727T235645.SAFE/GRANULE/L2A_T17TNJ_A047513_20240727T161942/IMG_DATA/R10m/T17TNJ_20240727T161831_B03_10m.jp2";
if !Path::new(b03_path).exists() {
eprintln!("SKIP: B03 file not found");
return;
}
match read(b03_path) {
Ok(raster) => {
eprintln!("[ll_cb_isolation] Raster decoded successfully: {}x{}", raster.cols, raster.rows);
}
Err(e) => {
eprintln!("ERROR: {}", e);
}
}
}
}