//! 진단용 — `wmbig` 오탐의 실제 근거 수치를 찍는다. //! `cargo test -p archive-indexer --test wmbig_evidence -- --nocapture` use archive_indexer::sigmatch::{self, FrameSig}; use std::path::{Path, PathBuf}; fn repo_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../..").canonicalize().unwrap() } fn sig_of(p: &Path) -> Option { let img = image::ImageReader::open(p).ok()?.with_guessed_format().ok()?.decode().ok()?; let gray = img .resize_exact( sigmatch::FRAME as u32, sigmatch::FRAME as u32, image::imageops::FilterType::Triangle, ) .to_luma8() .into_raw(); sigmatch::derive(&gray) } fn color_of(p: &Path) -> Option<[i8; 32]> { let img = image::ImageReader::open(p).ok()?.with_guessed_format().ok()?.decode().ok()?; let small = img.resize_exact(4, 4, image::imageops::FilterType::Triangle).to_rgb8().into_raw(); sigmatch::color_of(&small) } /// 임계값을 다시 손볼 때 근거표를 뽑는 용도. 단정이 없으므로 기본 실행에서는 뺀다. /// `sigmatch`의 VETO_AGREE_RATIO·CROP_MAX_DIST 주석에 적힌 수치가 여기서 나온 것이다. #[test] #[ignore = "근거 측정용 — cargo test --test wmbig_evidence -- --ignored --nocapture"] fn print_wmbig_evidence() { let dir = repo_root().join(".fixtures/images"); if !dir.is_dir() { eprintln!("픽스처 없음 — 건너뜀"); return; } let pairs = [ ("pos_still3_wmbig.jpg", "pos_still4_wmbig.jpg", "오탐: 서로 다른 원본 + 같은 큰 가림"), ("pos_still3_orig.jpg", "pos_still4_orig.jpg", "그 두 원본 자체"), ("pos_still3_orig.jpg", "pos_still3_wmbig.jpg", "정탐: 원본 ↔ 자기 가림본"), ("pos_still3_orig.jpg", "pos_still3_rot90.jpg", "회전본"), ("pos_still3_orig.jpg", "pos_still3_crop95.jpg", "5% 크롭"), ("pos_still3_orig.jpg", "pos_still3_q12.jpg", "저품질 재인코딩 (거부권에 걸리면 안 된다)"), ("pos_still3_orig.jpg", "pos_still3_half.jpg", "50% 축소"), ("pos_still3_orig.jpg", "pos_still3_bright.jpg", "밝기·대비 변경"), ("pos_still3_orig.jpg", "pos_still3_wm.jpg", "하단 자막 바"), ("neg_bars.jpg", "neg_hdbars.jpg", "무관: 컬러바 두 종"), ]; for (a, b, label) in pairs { let (pa, pb) = (dir.join(a), dir.join(b)); let (Some(sa), Some(sb)) = (sig_of(&pa), sig_of(&pb)) else { eprintln!("{label}: 파일 없음"); continue; }; let ra = sigmatch::RichSig { dhash: sa.dhash, orient: sa.orient, tiles: sa.tiles, scales: sa.scales, tile_flat: sa.tile_flat, color: color_of(&pa), }; let rb = sigmatch::RichSig { dhash: sb.dhash, orient: sb.orient, tiles: sb.tiles, scales: sb.scales, tile_flat: sb.tile_flat, color: color_of(&pb), }; let whole = sigmatch::hamming(ra.dhash, rb.dhash); let (agree_loose, seen) = sigmatch::informative_agreement(&ra, &rb, sigmatch::TILE_PER_TILE); let (agree_strict, _) = sigmatch::informative_agreement(&ra, &rb, sigmatch::TILE_STRICT_PER_TILE); let cdist = match (&ra.color, &rb.color) { (Some(x), Some(y)) => sigmatch::color_distance(x, y) as i64, _ => -1, }; let verdict = sigmatch::verify_pair(&ra, &rb, 5, true); println!("── {label}"); println!(" 전체 dHash 거리 {whole} · 색차 {cdist} · 판정 {verdict:?}"); println!( " 정보타일 {seen}개 중 근접 {agree_loose}(느슨 ≤{})/{agree_strict}(엄격 ≤{})", sigmatch::TILE_PER_TILE, sigmatch::TILE_STRICT_PER_TILE ); // 타일별로 거리와 '정보 없음' 여부를 같이 본다 (X = 판정에서 제외) let cells: Vec = (0..16) .map(|i| { let d = sigmatch::hamming(ra.tiles[i], rb.tiles[i]); let skip = (ra.tile_flat >> i) & 1 == 1 || (rb.tile_flat >> i) & 1 == 1; format!("{}{d:>2}", if skip { "X" } else { " " }) }) .collect(); for row in 0..4 { println!(" {}", cells[row * 4..row * 4 + 4].join(" ")); } } }