code wiki / (root) / nx_finger_count.nx

nx_finger_count.nx source

↩ module page · 267 lines · 9172 B

1// nx_finger_count.nx -- Tier 6 anatomical-coherence finger-counter. 2// 3// THE canonical generative-AI failure mode: six fingers per hand, 4// or three, or seven, or two fingers fused into a paddle. Stable 5// Diffusion / Flux / Midjourney / DALL-E all produce this with 6// alarming frequency because they have no anatomical prior. 7// Substrate's bits-up answer: count fingers DETERMINISTICALLY via 8// horizontal scan-line crossings on a binary hand mask. 9// 10// Method: 11// Input: binary hand mask (0 = bg, 1 = hand), w x h, plus a 12// scan_y row index that cuts horizontally THROUGH the 13// fingers (above the palm, below the fingertips). Caller 14// determines scan_y from hand-orientation upstream. 15// For x = 0..w-1: track run-length of consecutive 1 pixels along 16// scan_y. Each contiguous run >= min_run_width is one 17// finger. 18// verdict by run count vs anatomical norm (5 per hand): 19// 5 -> OK 20// 6+ -> EXTRA (the famous AI failure) 21// 4 -> MISSING (or fused) 22// 3 or less -> FUSED / OCCLUSION 23// NOISE detected (many tiny runs) -> NOISY 24// 25// Caller responsibility: provide a HAND-segmented binary mask + a 26// VALID scan_y. Substrate provides the deterministic counter. 27// 28// Reference: Suk/Park 2010 "Hand gesture recognition based on 29// digital image processing using MATLAB" -- scan-line approach for 30// finger counting in segmented hand regions. Convex-hull-defect 31// approach (Hawley/Eichmann 2014) is more robust to fingertip 32// occlusion; queued for nx_finger_count_v2. 33// 34// genealogy_id: suk_park_2010_scanline 35// lineage_id: substrate_anatomy_coherence_v1 36 37// nx_safety_envelope: 38// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 39// sil_target: SIL1 40// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 41// verdict: NOT_YET_EVALUATED 42 43import "nx_syscalls.nx" 44import "nx_runtime.nx" 45import "nx_tier.nx" 46 47// Sealed-verdict IDs. 48const NX_FC_VERDICT_OK: nx_int = 0 49const NX_FC_VERDICT_EXTRA: nx_int = 1 50const NX_FC_VERDICT_MISSING: nx_int = 2 51const NX_FC_VERDICT_FUSED: nx_int = 3 52const NX_FC_VERDICT_NOISY: nx_int = 4 53 54// Anatomical norm. 55const NX_FC_EXPECTED_FINGERS: nx_int = 5 56 57// Default minimum-run-width in pixels (used when caller passes 0). 58const NX_FC_DEFAULT_MIN_RUN: nx_int = 3 59const NX_FC_MAX_RUN_THR: nx_int = 1024 60const NX_FC_NOISE_RUN_LIMIT: nx_int = 16 // > this many tiny runs -> NOISY 61 62const NX_FC_MAX_DIM: nx_int = 16384 63 64// ===== result struct ============================================== 65 66struct NxFingerCountResult { 67 n_runs: nx_int, 68 n_tiny_runs: nx_int, 69 longest_run: nx_int, 70 expected: nx_int, 71 delta: nx_int, 72 verdict: nx_int, 73 scan_y: nx_int, 74 min_run_width: nx_int, 75} 76 77const NX_FC_RESULT_BYTES: nx_size = 64 78 79// ===== compute ==================================================== 80 81func nx_finger_count_scanline( 82 mask: *nx_int, w: nx_int, h: nx_int, 83 scan_y: nx_int, min_run_width: nx_int) -> *NxFingerCountResult { 84 85 if w <= 0 { return 0 as *NxFingerCountResult } 86 if h <= 0 { return 0 as *NxFingerCountResult } 87 if w > NX_FC_MAX_DIM { return 0 as *NxFingerCountResult } 88 if h > NX_FC_MAX_DIM { return 0 as *NxFingerCountResult } 89 if scan_y < 0 { return 0 as *NxFingerCountResult } 90 if scan_y >= h { return 0 as *NxFingerCountResult } 91 var thr: nx_int = min_run_width 92 if thr <= 0 { thr = NX_FC_DEFAULT_MIN_RUN } 93 if thr > NX_FC_MAX_RUN_THR { thr = NX_FC_MAX_RUN_THR } 94 95 let r_ptr: *u8 = sys_mmap(NX_FC_RESULT_BYTES) 96 let r: *NxFingerCountResult = r_ptr as *NxFingerCountResult 97 98 var n_runs: nx_int = 0 99 var n_tiny: nx_int = 0 100 var longest: nx_int = 0 101 var cur_run: nx_int = 0 102 var x: nx_int = 0 103 let row_base: nx_int = scan_y * w 104 while x < w { 105 let v: nx_int = mask[row_base + x] 106 if v != 0 { 107 cur_run = cur_run + 1 108 } else { 109 if cur_run > 0 { 110 if cur_run > longest { longest = cur_run } 111 if cur_run >= thr { 112 n_runs = n_runs + 1 113 } else { 114 n_tiny = n_tiny + 1 115 } 116 cur_run = 0 117 } 118 } 119 x = x + 1 120 } 121 // Close trailing run. 122 if cur_run > 0 { 123 if cur_run > longest { longest = cur_run } 124 if cur_run >= thr { 125 n_runs = n_runs + 1 126 } else { 127 n_tiny = n_tiny + 1 128 } 129 } 130 131 let delta: nx_int = n_runs - NX_FC_EXPECTED_FINGERS 132 133 var verdict: nx_int = NX_FC_VERDICT_OK 134 if n_tiny > NX_FC_NOISE_RUN_LIMIT { 135 verdict = NX_FC_VERDICT_NOISY 136 } else { 137 if n_runs == NX_FC_EXPECTED_FINGERS { 138 verdict = NX_FC_VERDICT_OK 139 } else { 140 if n_runs > NX_FC_EXPECTED_FINGERS { 141 verdict = NX_FC_VERDICT_EXTRA 142 } else { 143 if n_runs == 4 { 144 verdict = NX_FC_VERDICT_MISSING 145 } else { 146 verdict = NX_FC_VERDICT_FUSED 147 } 148 } 149 } 150 } 151 152 r.n_runs = n_runs 153 r.n_tiny_runs = n_tiny 154 r.longest_run = longest 155 r.expected = NX_FC_EXPECTED_FINGERS 156 r.delta = delta 157 r.verdict = verdict 158 r.scan_y = scan_y 159 r.min_run_width = thr 160 return r 161} 162 163// ===== self-test ================================================== 164// 165// Build small synthetic hand masks at scan_y rows. Each test uses 166// a 40-wide, 1-tall row (h=1, scan_y=0). Pattern uses ASCII-style 167// arrays where '1' = hand pixel, '0' = bg. 168 169func _fc_set_run(mask: *nx_int, x0: nx_int, n: nx_int) -> nx_int { 170 var i: nx_int = 0 171 while i < n { 172 mask[x0 + i] = 1 173 i = i + 1 174 } 175 return 0 176} 177 178func _fc_zero_row(mask: *nx_int, w: nx_int) -> nx_int { 179 var i: nx_int = 0 180 while i < w { 181 mask[i] = 0 182 i = i + 1 183 } 184 return 0 185} 186 187func main() -> nx_int { 188 // ---- OK hand: 5 fingers, each 4px wide, 2px gap ---- 189 // 190 // Layout (40 cols): GFFF GFFF GFFF GFFF GFFF (G=gap 2, F=finger 4) 191 // Positions: finger starts at 2, 8, 14, 20, 26. Each is 4 wide. 192 let m_ok: *nx_int = (sys_mmap(320)) as *nx_int 193 _fc_zero_row(m_ok, 40) 194 _fc_set_run(m_ok, 2, 4) 195 _fc_set_run(m_ok, 8, 4) 196 _fc_set_run(m_ok, 14, 4) 197 _fc_set_run(m_ok, 20, 4) 198 _fc_set_run(m_ok, 26, 4) 199 200 let r_ok: *NxFingerCountResult = nx_finger_count_scanline(m_ok, 40, 1, 0, 3) 201 if r_ok == (0 as *NxFingerCountResult) { return 1 } 202 if r_ok.n_runs != 5 { return 2 } 203 if r_ok.verdict != NX_FC_VERDICT_OK { return 3 } 204 if r_ok.longest_run != 4 { return 4 } 205 if r_ok.delta != 0 { return 5 } 206 207 // ---- EXTRA: 6 fingers ---- 208 let m_ex: *nx_int = (sys_mmap(320)) as *nx_int 209 _fc_zero_row(m_ex, 40) 210 _fc_set_run(m_ex, 2, 4) 211 _fc_set_run(m_ex, 8, 4) 212 _fc_set_run(m_ex, 14, 4) 213 _fc_set_run(m_ex, 20, 4) 214 _fc_set_run(m_ex, 26, 4) 215 _fc_set_run(m_ex, 32, 4) 216 let r_ex: *NxFingerCountResult = nx_finger_count_scanline(m_ex, 40, 1, 0, 3) 217 if r_ex.n_runs != 6 { return 10 } 218 if r_ex.verdict != NX_FC_VERDICT_EXTRA { return 11 } 219 if r_ex.delta != 1 { return 12 } 220 221 // ---- MISSING (or fused into 4): 4 fingers ---- 222 let m_mi: *nx_int = (sys_mmap(320)) as *nx_int 223 _fc_zero_row(m_mi, 40) 224 _fc_set_run(m_mi, 2, 4) 225 _fc_set_run(m_mi, 8, 4) 226 _fc_set_run(m_mi, 14, 4) 227 _fc_set_run(m_mi, 20, 4) 228 let r_mi: *NxFingerCountResult = nx_finger_count_scanline(m_mi, 40, 1, 0, 3) 229 if r_mi.n_runs != 4 { return 20 } 230 if r_mi.verdict != NX_FC_VERDICT_MISSING { return 21 } 231 232 // ---- FUSED: 3 fingers (e.g. two pairs fused into single wide run) 233 let m_fu: *nx_int = (sys_mmap(320)) as *nx_int 234 _fc_zero_row(m_fu, 40) 235 _fc_set_run(m_fu, 2, 4) 236 _fc_set_run(m_fu, 8, 10) // fused chunk 237 _fc_set_run(m_fu, 22, 4) 238 let r_fu: *NxFingerCountResult = nx_finger_count_scanline(m_fu, 40, 1, 0, 3) 239 if r_fu.n_runs != 3 { return 30 } 240 if r_fu.verdict != NX_FC_VERDICT_FUSED { return 31 } 241 if r_fu.longest_run != 10 { return 32 } 242 243 // ---- NOISY: > 16 tiny single-pixel runs interleaved. 244 // Use a 40-wide row with alternating 1010101010... 245 // -> 20 single-pixel runs. All under min_run_width=3 -> tiny. 246 let m_no: *nx_int = (sys_mmap(320)) as *nx_int 247 _fc_zero_row(m_no, 40) 248 var i: nx_int = 0 249 while i < 40 { 250 if i % 2 == 0 { m_no[i] = 1 } 251 i = i + 1 252 } 253 let r_no: *NxFingerCountResult = nx_finger_count_scanline(m_no, 40, 1, 0, 3) 254 if r_no.n_runs != 0 { return 40 } 255 if r_no.n_tiny_runs != 20 { return 41 } 256 if r_no.verdict != NX_FC_VERDICT_NOISY { return 42 } 257 258 // ---- input validation ---- 259 let r_bad: *NxFingerCountResult = nx_finger_count_scanline( 260 m_ok, 0, 1, 0, 3) 261 if r_bad != (0 as *NxFingerCountResult) { return 50 } 262 let r_bad_y: *NxFingerCountResult = nx_finger_count_scanline( 263 m_ok, 40, 1, 5, 3) 264 if r_bad_y != (0 as *NxFingerCountResult) { return 51 } 265 266 return 0 267}