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}