code wiki / (root) / nx_image_grade_cli.nx

nx_image_grade_cli.nx source

↩ module page · 241 lines · 8176 B

1// nx_image_grade_cli.nx -- substrate's image-grading CLI binary. 2// 3// CAPABILITY_COMPLETENESS: PARTIAL 4// MISSING_CAPABILITIES: 5// - argv-driven file-path input: substrate's `main()` has no 6// argv access in the current bootstrap convention; v1 reads 7// image bytes from STDIN. Calling pattern: 8// cat image.jpg | nishi-image-grade (or) 9// nishi-image-grade < image.jpg 10// - structured output: v1 emits one human-readable line per 11// axis; v2 (queued) emits canonical JSONL via 12// nx_jsonl_writer so callers can compose against the 13// ledger. 14// - exit-code mapping per failure tier: v1 returns 0 on 15// decode success, 1 on decoder error. 16// 17// COVERED: 18// - stdin -> bytes (up to 16 MiB cap) 19// - format auto-detect via nx_image_grade_v2 20// - PNG / JPEG decode + skin mask + segmenter + landmark 21// labeler + 8 axes invoked on real pixel data 22// - per-axis line emitted to stdout: "axis_id status verdict 23// score phrase" 24// - stderr message on decode error 25// 26// Usage: 27// nxc2 --target x86_64 nx_image_grade_cli.nx > cli.s 28// nx_assembler cli.s -> cli.elf 29// cat image.jpg | ./cli.elf 30// 31// genealogy_id: substrate_image_grade_cli_2026_05_16 32// lineage_id: nx_image_grade_cli_v1 33 34import "nx_syscalls.nx" 35import "nx_runtime.nx" 36import "nx_tier.nx" 37import "nx_image_grade_v2.nx" 38const NX_MAGIC_65536: i64 = 65536 39 40const NX_CLI_MAX_IMAGE_BYTES: nx_int = 16777216 // 16 MiB 41const NX_CLI_READ_CHUNK: nx_int = 65536 // 64 KiB read chunks 42const NX_CLI_LINE_BUF: nx_int = 256 43 44// ===== stdin slurp =============================================== 45// 46// Reads from fd 0 until EOF or cap. Returns bytes-read. 47 48func _cli_slurp_stdin(buf: *u8, max: nx_int) -> nx_int { 49 var total: nx_int = 0 50 var keep: nx_int = 1 51 var safety: nx_int = 0 52 let MAX_ITER: nx_int = NX_MAGIC_65536 53 while keep == 1 { 54 if safety >= MAX_ITER { keep = 0 } 55 else { 56 safety = safety + 1 57 if total >= max { keep = 0 } 58 else { 59 var chunk: nx_int = NX_CLI_READ_CHUNK 60 if (max - total) < chunk { chunk = max - total } 61 let dst: *u8 = (buf as nx_int + total) as *u8 62 let n: nx_int = sys_read(0, dst, chunk) 63 if n <= 0 { keep = 0 } 64 else { total = total + n } 65 } 66 } 67 } 68 return total 69} 70 71// ===== text helpers ============================================== 72// 73// Format integer as ASCII into buf; return new offset. 74 75func _cli_write_int(buf: *u8, off: nx_int, v: nx_int) -> nx_int { 76 if v == 0 { 77 buf[off] = 48 as u8 78 return off + 1 79 } 80 var val: nx_int = v 81 var negative: nx_int = 0 82 if val < 0 { 83 negative = 1 84 val = 0 - val 85 } 86 let scratch: *u8 = (sys_mmap(32)) as *u8 87 var n_digits: nx_int = 0 88 while val > 0 { 89 let d: nx_int = val % 10 90 scratch[n_digits] = (48 + d) as u8 91 val = val / 10 92 n_digits = n_digits + 1 93 } 94 var pos: nx_int = off 95 if negative == 1 { 96 buf[pos] = 45 as u8 // '-' 97 pos = pos + 1 98 } 99 var k: nx_int = n_digits - 1 100 while k >= 0 { 101 buf[pos] = scratch[k] 102 pos = pos + 1 103 k = k - 1 104 } 105 return pos 106} 107 108// Copy NUL-terminated string into buf at off; return new offset. 109func _cli_write_cstr(buf: *u8, off: nx_int, src: *u8, cap: nx_int) -> nx_int { 110 var pos: nx_int = off 111 var i: nx_int = 0 112 var safety: nx_int = 0 113 let MAX: nx_int = NX_CLI_LINE_BUF 114 while safety < MAX { 115 let ch: nx_int = (src[i] as nx_int) & 255 116 if ch == 0 { return pos } 117 if pos >= cap { return pos } 118 buf[pos] = ch as u8 119 pos = pos + 1 120 i = i + 1 121 safety = safety + 1 122 } 123 return pos 124} 125 126// Append a literal NUL-terminated byte sequence. 127func _cli_write_lit(buf: *u8, off: nx_int, lit: *u8) -> nx_int { 128 return _cli_write_cstr(buf, off, lit, NX_CLI_LINE_BUF) 129} 130 131// ===== status -> text label ===================================== 132 133func _cli_status_label(status: nx_int) -> *u8 { 134 if status == NX_IGV2_AXIS_OK { return "OK" as *u8 } 135 if status == NX_IGV2_AXIS_PENDING_KEYPOINT { return "PENDING_KEYPOINT" as *u8 } 136 if status == NX_IGV2_AXIS_PENDING_POSE_ESTIMATE { return "PENDING_POSE" as *u8 } 137 if status == NX_IGV2_AXIS_PENDING_VLM { return "PENDING_VLM" as *u8 } 138 if status == NX_IGV2_AXIS_NOT_APPLICABLE { return "N/A" as *u8 } 139 if status == NX_IGV2_AXIS_NO_SKIN_DETECTED { return "NO_SKIN" as *u8 } 140 if status == NX_IGV2_AXIS_INSUFFICIENT_LANDMARKS { return "PARTIAL_LANDMARKS" as *u8 } 141 return "UNKNOWN" as *u8 142} 143 144// ===== emit one axis line ======================================== 145// 146// Format: 147// axis=<id> status=<label> verdict=<int> score=<int> "<phrase>" 148// 149// Each line ends with '\n'. 150 151func _cli_emit_axis(ax: *NxIgv2AxisVerdict) -> nx_int { 152 let line: *u8 = (sys_mmap(NX_CLI_LINE_BUF as nx_size)) as *u8 153 var pos: nx_int = 0 154 pos = _cli_write_lit(line, pos, "axis=" as *u8) 155 pos = _cli_write_int(line, pos, ax.axis_id) 156 pos = _cli_write_lit(line, pos, " status=" as *u8) 157 pos = _cli_write_cstr(line, pos, _cli_status_label(ax.status), NX_CLI_LINE_BUF) 158 pos = _cli_write_lit(line, pos, " verdict=" as *u8) 159 pos = _cli_write_int(line, pos, ax.verdict_value) 160 pos = _cli_write_lit(line, pos, " score=" as *u8) 161 pos = _cli_write_int(line, pos, ax.score_q10) 162 pos = _cli_write_lit(line, pos, " phrase=" as *u8) 163 line[pos] = 34 as u8 // '"' 164 pos = pos + 1 165 pos = _cli_write_cstr(line, pos, ax.phrase_buf, NX_CLI_LINE_BUF - 3) 166 line[pos] = 34 as u8 // '"' 167 pos = pos + 1 168 line[pos] = 10 as u8 // '\n' 169 pos = pos + 1 170 sys_write(1, line, pos) 171 return 0 172} 173 174// ===== emit header line ========================================== 175 176func _cli_emit_header(r: *NxIgv2Report) -> nx_int { 177 let line: *u8 = (sys_mmap(NX_CLI_LINE_BUF as nx_size)) as *u8 178 var pos: nx_int = 0 179 pos = _cli_write_lit(line, pos, "image_grade_v2 format=" as *u8) 180 pos = _cli_write_int(line, pos, r.source_format) 181 pos = _cli_write_lit(line, pos, " w=" as *u8) 182 pos = _cli_write_int(line, pos, r.width) 183 pos = _cli_write_lit(line, pos, " h=" as *u8) 184 pos = _cli_write_int(line, pos, r.height) 185 pos = _cli_write_lit(line, pos, " err=" as *u8) 186 pos = _cli_write_int(line, pos, r.error_code) 187 pos = _cli_write_lit(line, pos, " n_axes=" as *u8) 188 pos = _cli_write_int(line, pos, r.n_axes) 189 pos = _cli_write_lit(line, pos, " skin_pixels=" as *u8) 190 pos = _cli_write_int(line, pos, r.skin_pixel_count) 191 pos = _cli_write_lit(line, pos, " skin_blobs=" as *u8) 192 pos = _cli_write_int(line, pos, r.n_skin_blobs) 193 line[pos] = 10 as u8 194 pos = pos + 1 195 sys_write(1, line, pos) 196 return 0 197} 198 199// ===== main ====================================================== 200 201func main() -> nx_int { 202 // Slurp stdin into a buffer. 203 let buf: *u8 = (sys_mmap(NX_CLI_MAX_IMAGE_BYTES as nx_size)) as *u8 204 let n: nx_int = _cli_slurp_stdin(buf, NX_CLI_MAX_IMAGE_BYTES) 205 if n <= 0 { 206 let msg: *u8 = "nx_image_grade_cli: no input on stdin\n" as *u8 207 sys_write(2, msg, 38) 208 return 1 209 } 210 211 // Trigger the v2 grader. 212 let r: *NxIgv2Report = nx_image_grade_v2(buf, n) 213 if r == (0 as *NxIgv2Report) { 214 let msg2: *u8 = "nx_image_grade_cli: grader returned null\n" as *u8 215 sys_write(2, msg2, 41) 216 return 2 217 } 218 219 // Emit header. 220 _cli_emit_header(r) 221 222 // When the decoder failed (e.g. NX_IGV2_ERR_DECODER on a PNG whose 223 // DEFLATE stream we can't yet inflate), grade_v2 leaves n_axes 224 // populated but the axes buffer holds junk. Walking it segfaults. 225 if r.error_code != NX_IGV2_OK { 226 return 1 227 } 228 229 // Emit per-axis lines. 230 if r.axes != (0 as *NxIgv2AxisVerdict) { 231 var i: nx_int = 0 232 while i < r.n_axes { 233 let ax: *NxIgv2AxisVerdict = 234 (r.axes as *u8 + (i as nx_size) * NX_IGV2_AXIS_VERDICT_BYTES) as *NxIgv2AxisVerdict 235 _cli_emit_axis(ax) 236 i = i + 1 237 } 238 } 239 240 return 0 241}