code wiki / (root) / nx_manga_ocr_gate.nx

nx_manga_ocr_gate.nx source

↩ module page · 106 lines · 5483 B

1// nx_manga_ocr_gate.nx -- REFEREE for MG22 (mo_text_regions). IN-PROCESS against nx_manga_ocr. 2// 3// THE FIXTURE IS PLANTED, NOT DECODED. Every ink map below is written by this gate at known 4// coordinates, so the EXPECTED ANSWER IS EXACT rather than eyeballed -- no image decode, no GPU, no 5// corpus, and the whole run is deterministic. A detector graded against a picture someone looked at is 6// graded by an interpretation; this one is graded by arithmetic. 7// 8// ANTI-VACUITY IS THE POINT. The trivial wrong implementation returns ONE bounding box around all the 9// ink and would satisfy any tooth that merely asks did-you-find-something. T3 requires TWO separated 10// blocks to come back as TWO with their own bounds, and T6 requires two lines a SMALL gap apart to 11// come back as ONE -- a detector that always splits fails T6, a detector that always merges fails T3, 12// and no single wrong rule passes both. 13// license_tier: ORIGINAL expect_exit: 0 14import "nx_syscalls.nx" 15import "nx_gate_verdict.nx" 16import "nx_manga_ocr.nx" 17 18const MOG_W: i64 = 40 19const MOG_H: i64 = 40 20const MOG_N: i64 = 1600 21const MOG_OUT: i64 = 256 22 23func mog_eq(a: i64, b: i64) -> i64 { if a == b { return 1 } return 0 } 24 25func mog_clear(ink: *u8) -> i64 { var i: i64 = 0; while i < MOG_N { ink[i] = 0 as u8; i = i + 1 } return 0 } 26 27func mog_fill(ink: *u8, x0: i64, y0: i64, x1: i64, y1: i64) -> i64 { 28 var y: i64 = y0 29 while y <= y1 { 30 var x: i64 = x0 31 while x <= x1 { ink[y * MOG_W + x] = 1 as u8; x = x + 1 } 32 y = y + 1 33 } 34 return 0 35} 36 37// rect_is -- does region i carry exactly these bounds 38func mog_rect_is(out: *i64, i: i64, x0: i64, y0: i64, x1: i64, y1: i64) -> i64 { 39 if out[i * MO_FIELDS + 0] != x0 { return 0 } 40 if out[i * MO_FIELDS + 1] != y0 { return 0 } 41 if out[i * MO_FIELDS + 2] != x1 { return 0 } 42 if out[i * MO_FIELDS + 3] != y1 { return 0 } 43 return 1 44} 45 46func main(argc: i64, argv: *i64) -> i64 { 47 let ctr: *i64 = gv_ctr() 48 gv_head("nx_manga_ocr -- text regions recovered from a PLANTED ink map, expected answer exact" as *u8) 49 50 let ink: *u8 = sys_mmap(MOG_N) 51 let out: *i64 = sys_mmap(MOG_OUT * 8) as *i64 52 53 // ---- neg-control: an empty page has no text ---------------------------------------------- 54 mog_clear(ink) 55 let n0: i64 = mo_text_regions(ink, MOG_W, 0, 0, MOG_W, MOG_H, out) 56 gv_check("T1 neg-control-blank: a page with no ink yields ZERO regions -- the detector does not invent text" as *u8, mog_eq(n0, 0), ctr) 57 58 // ---- one block, exact bounds -------------------------------------------------------------- 59 mog_clear(ink) 60 mog_fill(ink, 5, 5, 14, 9) 61 let n1: i64 = mo_text_regions(ink, MOG_W, 0, 0, MOG_W, MOG_H, out) 62 gv_check("T2 one planted block is found exactly once" as *u8, mog_eq(n1, 1), ctr) 63 gv_check("T2b its bounds are EXACT -- x0 5 y0 5 x1 14 y1 9, arithmetic not eyeball" as *u8, mog_rect_is(out, 0, 5, 5, 14, 9), ctr) 64 65 // ---- ANTI-VACUITY: two well-separated blocks must stay TWO -------------------------------- 66 mog_clear(ink) 67 mog_fill(ink, 5, 5, 14, 9) 68 mog_fill(ink, 20, 25, 29, 29) 69 let n2: i64 = mo_text_regions(ink, MOG_W, 0, 0, MOG_W, MOG_H, out) 70 gv_check("T3 ANTI-VACUITY: two blocks 15 rows apart come back as TWO -- the one-box-around-all-ink implementation dies here" as *u8, mog_eq(n2, 2), ctr) 71 gv_check("T3b the FIRST region is the upper block with its own x extent, not a merged bound" as *u8, mog_rect_is(out, 0, 5, 5, 14, 9), ctr) 72 gv_check("T3c the SECOND region is the lower block at its own x offset -- proves per-band vertical projection" as *u8, mog_rect_is(out, 1, 20, 25, 29, 29), ctr) 73 74 // ---- the same rule in the other direction: a SMALL gap is leading, not a break ------------ 75 mog_clear(ink) 76 mog_fill(ink, 8, 5, 17, 7) 77 mog_fill(ink, 8, 9, 17, 11) 78 let n3: i64 = mo_text_regions(ink, MOG_W, 0, 0, MOG_W, MOG_H, out) 79 gv_check("T4 two lines ONE row apart merge into a single block -- an always-splits detector dies here" as *u8, mog_eq(n3, 1), ctr) 80 gv_check("T4b the merged block spans both lines, y 5 through 11" as *u8, mog_rect_is(out, 0, 8, 5, 17, 11), ctr) 81 82 // ---- speckle rejection: a single stray pixel is not text ---------------------------------- 83 mog_clear(ink) 84 ink[12 * MOG_W + 12] = 1 as u8 85 let n4: i64 = mo_text_regions(ink, MOG_W, 0, 0, MOG_W, MOG_H, out) 86 gv_check("T5 a lone stray pixel is REJECTED as speckle rather than reported as a text region" as *u8, mog_eq(n4, 0), ctr) 87 88 // ---- the window argument is honoured, not ignored ----------------------------------------- 89 mog_clear(ink) 90 mog_fill(ink, 5, 5, 14, 9) 91 mog_fill(ink, 20, 25, 29, 29) 92 let n5: i64 = mo_text_regions(ink, MOG_W, 0, 0, MOG_W, 20, out) 93 gv_check("T6 a search window CLIPS: bounding the scan to y under 20 finds only the upper block, so the window is read rather than ignored" as *u8, mog_eq(n5, 1), ctr) 94 95 gv_values_head() 96 gv_kv("blank_regions" as *u8, n0) 97 gv_kv("one_block_regions" as *u8, n1) 98 gv_kv("two_block_regions" as *u8, n2) 99 gv_kv("merged_line_regions" as *u8, n3) 100 gv_kv("speckle_regions" as *u8, n4) 101 gv_kv("windowed_regions" as *u8, n5) 102 gv_kv("line_gap" as *u8, MO_LINE_GAP) 103 gv_kv("min_ink" as *u8, MO_MIN_INK) 104 105 return gv_verdict("MANGA-OCR-GATE" as *u8, ctr, "text regions recovered exactly from planted ink, separated blocks stay separate, close lines merge, speckle refused, window honoured" as *u8) 106}