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}