code wiki / (root) / nx_img_threshold.nx

nx_img_threshold.nx source

↩ module page · 52 lines · 2025 B

1// nx_img_threshold.nx -- R2 of the sovereign scanning stack: grayscale -> binary (ink/paper). Otsu's method finds 2// the intensity threshold that maximizes between-class variance (the optimal split of a bimodal page histogram), 3// then binarizes to 0 (ink) / 255 (paper). Integer-exact (integer class means; no floats). Composes R1's 4// img_histogram. This is what turns a scanned page into clean text-vs-background for R3 segmentation. license_tier: ORIGINAL 5import "nx_syscalls.nx" 6import "nx_img_core.nx" 7 8// Otsu threshold from a 256-bin histogram + total pixel count. Returns t in [0,255]. 9func thr_otsu(hist: *i64, npix: i64) -> i64 { 10 if npix <= 0 { return 128 } 11 var total: i64 = 0 12 var i: i64 = 0 13 while i < 256 { total = total + i * hist[i]; i = i + 1 } 14 var wB: i64 = 0 15 var sumB: i64 = 0 16 var best_t: i64 = 0 17 var best_var: i64 = 0 - 1 18 var t: i64 = 0 19 while t < 256 { 20 wB = wB + hist[t] 21 sumB = sumB + t * hist[t] 22 if wB > 0 { if wB < npix { 23 let wF: i64 = npix - wB 24 let sumF: i64 = total - sumB 25 let mB: i64 = sumB / wB // integer background mean 26 let mF: i64 = sumF / wF // integer foreground mean 27 let d: i64 = mB - mF 28 let between: i64 = wB * wF * d * d 29 if between > best_var { best_var = between; best_t = t } 30 } } 31 t = t + 1 32 } 33 return best_t 34} 35 36// convenience: compute the Otsu threshold directly from a pixel buffer. 37func thr_otsu_of(pix: *u8, npix: i64) -> i64 { 38 let hist: *i64 = sys_mmap(256 * 8) as *i64 39 img_histogram(pix, npix, hist) 40 return thr_otsu(hist, npix) 41} 42 43// binarize in place: pixel <= t -> 0 (ink), else 255 (paper). Returns the ink-pixel count. 44func thr_binarize(pix: *u8, npix: i64, t: i64) -> i64 { 45 var i: i64 = 0; var fg: i64 = 0 46 while i < npix { 47 let v: i64 = pix[i] as i64 48 if v <= t { pix[i] = 0 as u8; fg = fg + 1 } else { pix[i] = 255 as u8 } 49 i = i + 1 50 } 51 return fg 52}