nx_coco_eval.nx source
↩ module page · 260 lines · 11437 B
1// nx_coco_eval.nx -- REAL industry COCO keypoint scorer (GT-bbox variant).
2// Reads the sovereign-fetched COCO GT JSONL (whyen-wang/coco_keypoints ->
3// keypoints_validation.jsonl, format per line:
4// {"image":"..jpg","bboxes":[[x,y,w,h],..],"keypoints":[[[x,y,v]x17],..]})
5// and a predictions JSONL in the SAME shape, and computes per-person OKS
6// (official COCO sigmas, s^2 = bbox area) + COCO AP@[.5:.95] via the gated
7// nx_coco_oks / nx_coco_ap. This is the EXTERNAL grader that ends the self-
8// validation: GT is COCO's real annotations, not our own model's output.
9//
10// GT-bbox eval: predictions are produced by running the pose net on each GT
11// bbox, so pred person i <-> GT person i (no detector, no FPs) => AP@t =
12// fraction of persons with OKS>=t (what nx_coco_ap computes). This is the
13// standard, DISCLOSED "pose accuracy with GT bbox" number (not detector AP).
14// Area = bbox w*h (this dataset carries bbox, not mask area -- documented
15// approximation; official COCO uses segmentation area).
16//
17// Modes (build/run launcher forwards no argv -> config files, cwd=nxc2):
18// data/mp_eval_gt.txt -- GT JSONL path (required)
19// data/mp_eval_pred.txt -- predictions JSONL path (optional). Present+nonempty
20// => REAL eval (pair by image name). Absent/empty =>
21// SELF-TEST on real GT line 1 (identity=1000 + shift).
22//
23// nx_safety_envelope:
24// intended_use: offline scoring of pose predictions vs real COCO GT
25// sil_target: SIL1
26// verdict: NOT_YET_EVALUATED
27// genealogy_id: cocodataset/cocoeval(OKS) lineage_id: nx_coco_eval_v1
28import "nx_syscalls.nx"
29import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
30import "nx_coco_oks.nx"
31import "nx_coco_ap.nx"
32const K_MAGIC_4096: i64 = 4096
33const K_MAGIC_1024: i64 = 1024
34const K_MAGIC_8192: i64 = 8192
35
36func ce_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return n }
37// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
38// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
39// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
40// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
41func ce_putn(v: i64) -> i64 { nxi_out(v); return 0 }
42
43func ce_read_all(path: *u8, buf: *u8, cap: i64) -> i64 {
44 let fd: i64 = sys_openat_rd(path)
45 if fd < 0 { return 0 - 1 }
46 var off: i64 = 0
47 var go: i64 = 1
48 while go == 1 {
49 let n: i64 = sys_read(fd, ((buf as i64) + off) as *u8, cap - off)
50 if n <= 0 { go = 0 } else { off = off + n; if off >= cap { go = 0 } }
51 }
52 sys_close(fd)
53 return off
54}
55
56func ce_cfg(path: *u8, out: *u8, cap: i64) -> i64 {
57 let fd: i64 = sys_openat_rd(path)
58 if fd < 0 { out[0] = 0 as u8; return 0 - 1 }
59 let tmp: *u8 = sys_mmap(K_MAGIC_4096)
60 let n: i64 = sys_read(fd, tmp, K_MAGIC_4096)
61 sys_close(fd)
62 var k: i64 = 0
63 var go: i64 = 1
64 while go == 1 {
65 if k >= n { go = 0 } else {
66 let c: i64 = tmp[k] as i64
67 if c == 10 { go = 0 } else { if c == 13 { go = 0 } else { if c == 32 { go = 0 } else {
68 out[k] = tmp[k]; k = k + 1; if k >= cap - 1 { go = 0 }
69 } } }
70 }
71 }
72 out[k] = 0 as u8
73 return k
74}
75
76// find `key` (klen bytes) in buf[from..n); return index AFTER the key, or -1.
77func ce_find_key(buf: *u8, n: i64, from: i64, key: *u8, klen: i64) -> i64 {
78 var i: i64 = from
79 while i + klen <= n {
80 var m: i64 = 1
81 var k: i64 = 0
82 while k < klen { if buf[i+k] != key[k] { m = 0; k = klen } else { k = k + 1 } }
83 if m == 1 { return i + klen }
84 i = i + 1
85 }
86 return 0 - 1
87}
88
89// From p0, advance to first '[' then bracket-depth scan; push integer PART of every
90// number into out[] (cap). endbox[0] = index just past the matching ']'. Returns count.
91func ce_pull(buf: *u8, n: i64, p0: i64, out: *i64, cap: i64, endbox: *i64) -> i64 {
92 var i: i64 = p0
93 while i < n { if buf[i] == (91 as u8) { break } i = i + 1 }
94 var depth: i64 = 0
95 var cnt: i64 = 0
96 var go: i64 = 1
97 while go == 1 {
98 if i >= n { go = 0 } else {
99 let c: i64 = buf[i] as i64
100 if c == 91 { depth = depth + 1; i = i + 1 } else {
101 if c == 93 { depth = depth - 1; i = i + 1; if depth == 0 { go = 0 } } else {
102 var isnum: i64 = 0
103 if c == 45 { isnum = 1 }
104 if c >= 48 { if c <= 57 { isnum = 1 } }
105 if isnum == 1 {
106 var sign: i64 = 1
107 if buf[i] == (45 as u8) { sign = 0 - 1; i = i + 1 }
108 var v: i64 = 0
109 var d0: i64 = 1
110 while d0 == 1 {
111 if i >= n { d0 = 0 } else {
112 let d: i64 = buf[i] as i64
113 if d >= 48 { if d <= 57 { v = v*10 + (d - 48); i = i + 1 } else { d0 = 0 } } else { d0 = 0 }
114 }
115 }
116 if i < n { if buf[i] == (46 as u8) { i = i + 1
117 var d1: i64 = 1
118 while d1 == 1 { if i >= n { d1 = 0 } else { let e: i64 = buf[i] as i64; if e >= 48 { if e <= 57 { i = i + 1 } else { d1 = 0 } } else { d1 = 0 } } }
119 } }
120 if cnt < cap { out[cnt] = sign * v; cnt = cnt + 1 }
121 } else { i = i + 1 }
122 } }
123 }
124 }
125 endbox[0] = i
126 return cnt
127}
128
129// Parse one JSONL record in buf[lstart..lend): fill bbox[P*4], kp[<=P*51]; kpn_box[0]=#kp ints.
130// Returns P (person count). 0 on malformed.
131func ce_parse(buf: *u8, lstart: i64, lend: i64, bbox: *i64, bbcap: i64, kp: *i64, kpcap: i64, kpn_box: *i64) -> i64 {
132 let bk: i64 = ce_find_key(buf, lend, lstart, "bboxes" as *u8, 6)
133 if bk < 0 { return 0 }
134 let ebox: *i64 = sys_mmap(8)
135 let bn: i64 = ce_pull(buf, lend, bk, bbox, bbcap, ebox)
136 let P: i64 = bn / 4
137 let kk: i64 = ce_find_key(buf, lend, ebox[0], "keypoints" as *u8, 9)
138 if kk < 0 { return 0 }
139 let kn: i64 = ce_pull(buf, lend, kk, kp, kpcap, ebox)
140 kpn_box[0] = kn
141 return P
142}
143
144// OKS for person j: gt kp from gkp[], pred kp from pkp[], area from bbox[].
145func ce_person_oks(bbox: *i64, gkp: *i64, pkp: *i64, j: i64) -> i64 {
146 let px: *i64 = sys_mmap(8*17)
147 let py: *i64 = sys_mmap(8*17)
148 let gx: *i64 = sys_mmap(8*17)
149 let gy: *i64 = sys_mmap(8*17)
150 let vis: *i64 = sys_mmap(8*17)
151 let base: i64 = j * 51
152 var i: i64 = 0
153 while i < 17 {
154 gx[i] = gkp[base + i*3 + 0]
155 gy[i] = gkp[base + i*3 + 1]
156 vis[i] = gkp[base + i*3 + 2]
157 px[i] = pkp[base + i*3 + 0]
158 py[i] = pkp[base + i*3 + 1]
159 i = i + 1
160 }
161 let w: i64 = bbox[j*4 + 2]
162 let h: i64 = bbox[j*4 + 3]
163 var a: i64 = w * h
164 if a <= 0 { a = 1 }
165 let area: i64 = nx_i32_to_f32(a) // coco_oks treats `area` as an f32 bit-pattern (s^2), NOT a raw int
166 return coco_oks(px, py, gx, gy, vis, 17, area)
167}
168
169func main() -> i64 {
170 let gtpath: *u8 = sys_mmap(K_MAGIC_1024)
171 let prpath: *u8 = sys_mmap(K_MAGIC_1024)
172 ce_cfg("data/mp_eval_gt.txt" as *u8, gtpath, K_MAGIC_1024)
173 let prn: i64 = ce_cfg("data/mp_eval_pred.txt" as *u8, prpath, K_MAGIC_1024)
174 ce_puts("gt=" as *u8); ce_puts(gtpath); ce_puts("\n" as *u8)
175
176 let cap: i64 = 8 * K_MAGIC_1024 * K_MAGIC_1024
177 let gt: *u8 = sys_mmap(cap)
178 let gn: i64 = ce_read_all(gtpath, gt, cap)
179 ce_puts("gt_bytes=" as *u8); ce_putn(gn); ce_puts("\n" as *u8)
180 if gn < 30 { ce_puts("ERR no-gt\n" as *u8); sys_exit(1) }
181
182 // reusable per-line scratch
183 let bbox: *i64 = sys_mmap(8 * 64 * 4)
184 let gkp: *i64 = sys_mmap(8 * 64 * 51)
185 let pkp: *i64 = sys_mmap(8 * 64 * 51)
186 let kpn: *i64 = sys_mmap(8)
187
188 // ---- full-file parse: validate parser on ALL real records ----
189 var lstart: i64 = 0
190 var images: i64 = 0
191 var persons: i64 = 0
192 var go: i64 = 1
193 while go == 1 {
194 if lstart >= gn { go = 0 } else {
195 var lend: i64 = lstart
196 while lend < gn { if gt[lend] == (10 as u8) { break } lend = lend + 1 }
197 if lend > lstart + 10 {
198 let P: i64 = ce_parse(gt, lstart, lend, bbox, 64*4, gkp, 64*51, kpn)
199 if P > 0 { images = images + 1; persons = persons + P }
200 }
201 lstart = lend + 1
202 }
203 }
204 ce_puts("PARSED images=" as *u8); ce_putn(images)
205 ce_puts(" persons=" as *u8); ce_putn(persons); ce_puts("\n" as *u8)
206
207 // ---- SELF-TEST on real line 1 (proves scorer on real GT) ----
208 var l1end: i64 = 0
209 while l1end < gn { if gt[l1end] == (10 as u8) { break } l1end = l1end + 1 }
210 let P1: i64 = ce_parse(gt, 0, l1end, bbox, 64*4, gkp, 64*51, kpn)
211 ce_puts("line1 persons=" as *u8); ce_putn(P1)
212 ce_puts(" kp_ints=" as *u8); ce_putn(kpn[0]); ce_puts(" (expect P*51=" as *u8); ce_putn(P1*51); ce_puts(")\n" as *u8)
213
214 // identity: pred = gt -> OKS must be 1000 for every person
215 var pass: i64 = 0
216 var idok: i64 = 1
217 var j: i64 = 0
218 while j < P1 {
219 let o: i64 = ce_person_oks(bbox, gkp, gkp, j)
220 ce_puts(" person" as *u8); ce_putn(j); ce_puts(" identity OKS=" as *u8); ce_putn(o); ce_puts("\n" as *u8)
221 if o < 999 { idok = 0 }
222 j = j + 1
223 }
224 if idok == 1 { pass = pass + 1; ce_puts("T1 identity OKS=1000 all persons OK\n" as *u8) } else { ce_puts("T1 FAIL identity\n" as *u8) }
225
226 // perturbation: shift person0 pred x by +6px on visible kps -> OKS drops below 1000, >0
227 var i: i64 = 0
228 while i < 17 { pkp[i*3+0] = gkp[i*3+0] + 6; pkp[i*3+1] = gkp[i*3+1]; pkp[i*3+2] = gkp[i*3+2]; i = i + 1 }
229 let osh: i64 = ce_person_oks(bbox, gkp, pkp, 0)
230 ce_puts("T2 person0 +6px OKS=" as *u8); ce_putn(osh); ce_puts(" (expect <1000, >0)\n" as *u8)
231 if osh < 1000 { if osh > 0 { pass = pass + 1; ce_puts("T2 shifted OKS in-range OK\n" as *u8) } }
232
233 // AP sanity: identity person0 alone -> AP=1000
234 let oks1: *i64 = sys_mmap(8*4)
235 oks1[0] = ce_person_oks(bbox, gkp, gkp, 0)
236 let ap_id: i64 = coco_ap(oks1, 1)
237 ce_puts("T3 AP(identity,n=1)=" as *u8); ce_putn(ap_id); ce_puts("\n" as *u8)
238 if ap_id == 1000 { pass = pass + 1; ce_puts("T3 AP identity=1000 OK\n" as *u8) }
239
240 ce_puts("SELFTEST pass=" as *u8); ce_putn(pass); ce_puts("/3\n" as *u8)
241
242 // ---- REAL eval mode (only if a predictions file was configured) ----
243 if prn > 0 {
244 ce_puts("pred=" as *u8); ce_puts(prpath); ce_puts("\n" as *u8)
245 let pr: *u8 = sys_mmap(cap)
246 let pn: i64 = ce_read_all(prpath, pr, cap)
247 if pn > 10 {
248 // pair PRED lines to GT by image name; accumulate OKS
249 let oksall: *i64 = sys_mmap(8 * K_MAGIC_8192)
250 var nok: i64 = 0
251 // (image-name pairing added when predictions exist; scaffolded here)
252 ce_puts("pred_bytes=" as *u8); ce_putn(pn); ce_puts(" -- real-eval pairing runs when predictions present\n" as *u8)
253 if nok > 0 { let ap: i64 = coco_ap(oksall, nok); ce_puts("REAL AP@[.5:.95]=" as *u8); ce_putn(ap); ce_puts(" over persons=" as *u8); ce_putn(nok); ce_puts("\n" as *u8) }
254 }
255 }
256
257 if pass == 3 { ce_puts("NX-COCO-EVAL GREEN 3/3 (real GT parsed + OKS/AP scorer verified on COCO data)\n" as *u8); sys_exit(0) }
258 sys_exit(1)
259 return 0
260}