code wiki / _hdl_build / nx_silhouette.nx

nx_silhouette.nx source

↩ module page · 582 lines · 35008 B

1// nx_silhouette.nx -- ★★★PHOTO -> OUTLINE PROFILE: the missing link between a reference figure and a number. 2// 3// WHY THIS EXISTS. The curvature metric reads 3D geometry, so a photograph -- which is what every reference 4// figure actually is -- could not be measured at all. That left "aim at this figure" as an instruction with no 5// arithmetic behind it. This organ closes that: a photo becomes a half-width profile, and the profile goes into 6// the SAME curvature measurement the meshes use. 7// ★★ONE CURVATURE IMPLEMENTATION, TWO SOURCES. This organ deliberately does NOT compute curvature. It emits a 8// profile; nx_curvebench measures it. A second copy of the metric living here is how a photo target and a mesh 9// score would quietly drift onto different scales and stop being comparable -- which would defeat the entire 10// point of extracting an aim point in the first place. 11// 12// ★★AND IT IS THE SAME PIECE THE TWIN LOOP NEEDS. Registration (bringing a subject and the model into one 13// frame) starts from exactly this: the subject's outline. So the beauty lane and the capture ladder turn out to 14// want one capability, which is the second time in this programme that two goals have collapsed into one rung. 15// 16// SEGMENTATION: skin classified in CHROMA, not in brightness. The classic robust rule -- skin occupies a 17// compact region in Cb/Cr almost independently of lightness -- which is why it survives the hard sun-and-shade 18// mixtures in outdoor reference photography where a brightness threshold would cut the figure in half at the 19// shadow line. ★HONEST SCOPE, stated because it decides where this may be pointed: this works on a figure 20// against a non-skin background. It is not a general matting solution, it will fail on skin-toned backgrounds, 21// sand, and tanned wood, and T3 exists to prove it at least REFUSES rather than inventing a body. 22// 23// nx_silhouette profile <image> <out.prof> [bands] -> half-width profile + coverage 24// nx_silhouette selftest 25// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26). 26import "nx_gate_verdict.nx" 27// ★the decoder is COMPOSED, not rewritten. A second JPEG path in this organ would be a second definition of 28// what a pixel is, and the two would disagree the first time either was improved. 29import "nx_img_bytes_to_rgb.nx" 30const SL_MAGIC_1000000000: i64 = 1000000000 31 32const SL_BANDS: i64 = 128 33const SL_PREC: i64 = 100000 34const SL_MM: i64 = 1000 35const SL_MODE: i64 = 420 36// ★SKIN IN CHROMA. Cb/Cr bounds are the standard compact skin locus; the wide Y admission is deliberate so a 37// figure lit half in sun and half in shade stays ONE figure. 38const SL_CB_LO: i64 = 77 39const SL_CB_HI: i64 = 127 40const SL_CR_LO: i64 = 133 41const SL_CR_HI: i64 = 173 42const SL_Y_LO: i64 = 40 43// ★how close to the border still counts as touching it -- a couple of pixels of vignetting or JPEG ringing 44// should not decide whether a row bled, so the test is "at the border" with a small tolerance, not "at x=0". 45const SL_EDGE: i64 = 2 46// ★the refusal line, in per-mille of figure rows. Measured: synthetic fixtures bleed 0; the over-firing 47// reference photograph bleeds ~1000. Half the rows spanning border to border is already not a segmentation. 48const SL_MAXBLEED: i64 = 500 49// a row needs this many skin pixels before it counts as figure -- one stray pixel is not a body 50const SL_MINRUN: i64 = 3 51// below this fraction of rows carrying figure, the profile is not a measurement and the organ says so 52const SL_MINCOV: i64 = 250 53 54func sl_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 55func sl_pn(v: i64) -> i64 { 56 let b: *u8 = sys_mmap(32); var x: i64=v; var ng: i64=0 57 if x<0 { ng=1; x=0-x } 58 var i: i64=31 59 if x==0 { b[i]=48 as u8; i=i-1 } 60 while x>0 { b[i]=(48+x%10) as u8; x=x/10; i=i-1 } 61 if ng==1 { b[i]=45 as u8; i=i-1 } 62 sys_write(1,(b as i64 + i + 1) as *u8, 31-i); return 0 63} 64func sl_streq(a: *u8, b: *u8) -> i64 { 65 var i: i64=0; var go: i64=1; var eq: i64=1 66 while go==1 { if a[i]!=b[i] { eq=0; go=0 } else { if a[i]==(0 as u8) { go=0 } else { i=i+1 } } } 67 return eq 68} 69func sl_atoi(s: *u8) -> i64 { 70 var i: i64=0; var n: i64=0; var sg: i64=1 71 if s[0]==(45 as u8) { sg=0-1; i=1 } 72 while s[i]!=(0 as u8) { let c: i64=s[i] as i64; if c>=48 { if c<=57 { n=n*10+(c-48) } } i=i+1 } 73 return n*sg 74} 75func sl_wint(b: *u8, pos: *i64, v: i64, c: i64) -> i64 { 76 var x: i64=v 77 if x<0 { b[pos[0]]=45 as u8; pos[0]=pos[0]+1; x=0-x } 78 let t: *u8 = sys_mmap(32); var k: i64=0 79 if x==0 { t[0]=48 as u8; k=1 } 80 while x>0 { t[k]=(48+x%10) as u8; x=x/10; k=k+1 } 81 var q: i64=k-1 82 while q>=0 { b[pos[0]]=t[q]; pos[0]=pos[0]+1; q=q-1 } 83 b[pos[0]]=c as u8; pos[0]=pos[0]+1 84 return 0 85} 86// ★CHROMA SKIN TEST. Integer BT.601: the same transform the codec lane already uses, so a pixel called skin 87// here is called skin there too rather than by a second private definition of the same colour. 88func sl_is_skin(r: i64, g: i64, b: i64) -> i64 { 89 let y: i64 = (r*299 + g*587 + b*114)/1000 90 let cb: i64 = 128 + (b-y)*564/1000 91 let cr: i64 = 128 + (r-y)*713/1000 92 if y < SL_Y_LO { return 0 } 93 if cb < SL_CB_LO { return 0 } 94 if cb > SL_CB_HI { return 0 } 95 if cr < SL_CR_LO { return 0 } 96 if cr > SL_CR_HI { return 0 } 97 // ★and the ordering rule that kills most false positives: skin is R > G > B. Foliage and water satisfy the 98 // chroma box surprisingly often; they almost never satisfy the ordering as well. 99 if r <= g { return 0 } 100 if g <= b { return 0 } 101 return 1 102} 103// scan RGB into a half-width profile. out[0..bands-1] = half-width in 1/100000 of FIGURE HEIGHT, 104// out[bands] = rows with figure, out[bands+1] = coverage per-mille 105// 2D CONNECTED COMPONENTS, run-based with union-find. MEASURED REASON THIS EXISTS: taking the first and last 106// skin pixel in a row absorbed a tan UI swatch 400px from the subject and reported the GAP as a body width 107// (914 per-mille of rows contaminated, span 435 per-mille too wide, while edge_bleed read 0 and called the 108// frame CLEAN). The obvious repair -- the LONGEST CONTIGUOUS RUN -- is also wrong: a horizontal scan across a 109// face crosses cheek, sclera, bridge, sclera, cheek, so the longest run is ONE CHEEK (166px -> 47px against a 110// 125px face, measured A/B on the same frame; that build was promoted and then reverted). 111// THE POINT: an eye does not disconnect a face, because the cheek joins around it VERTICALLY. Only 2D 112// connectivity separates a HOLE INSIDE the subject from a SECOND OBJECT BESIDE it, and no row-local rule can 113// ever do it -- which is exactly why both 1D repairs failed, in opposite directions. 114const SL_MAXRUNS: i64 = 262144 115func sl_find(p: *i64, x: i64) -> i64 { 116 var r: i64 = x 117 while p[r] != r { r = p[r] } 118 var c: i64 = x 119 while p[c] != c { let n: i64 = p[c]; p[c] = r; c = n } 120 return r 121} 122// fill clo/chi with the LARGEST component's per-row extents; returns its pixel count, or negative to REFUSE. 123// A frame with more runs than the arena is REFUSED (-9), never silently truncated: a truncated labelling 124// would split the subject and report a fragment as the body, which is the failure this whole function exists 125// to end. 126func sl_ccl(rgb: *u8, w: i64, h: i64, clo: *i64, chi: *i64) -> i64 { 127 let rs: *i64 = sys_mmap(SL_MAXRUNS*8) as *i64 128 let re: *i64 = sys_mmap(SL_MAXRUNS*8) as *i64 129 let rr: *i64 = sys_mmap(SL_MAXRUNS*8) as *i64 130 let pa: *i64 = sys_mmap(SL_MAXRUNS*8) as *i64 131 let rf: *i64 = sys_mmap((h+8)*8) as *i64 132 let rc: *i64 = sys_mmap((h+8)*8) as *i64 133 var nr: i64 = 0 134 var y: i64 = 0 135 while y < h { 136 rf[y] = nr 137 var cnt: i64 = 0 138 var run: i64 = 0 139 var x: i64 = 0 140 while x < w { 141 let o: i64 = (y*w + x)*3 142 if sl_is_skin(rgb[o] as i64, rgb[o+1] as i64, rgb[o+2] as i64) == 1 { run = run + 1 } else { 143 if run >= SL_MINRUN { 144 if nr >= SL_MAXRUNS { return 0-9 } 145 rs[nr] = x - run; re[nr] = x - 1; rr[nr] = y; pa[nr] = nr; nr = nr + 1; cnt = cnt + 1 146 } 147 run = 0 148 } 149 x = x + 1 150 } 151 if run >= SL_MINRUN { 152 if nr >= SL_MAXRUNS { return 0-9 } 153 rs[nr] = w - run; re[nr] = w - 1; rr[nr] = y; pa[nr] = nr; nr = nr + 1; cnt = cnt + 1 154 } 155 rc[y] = cnt 156 y = y + 1 157 } 158 if nr == 0 { return 0-3 } 159 // union runs in ADJACENT ROWS that overlap horizontally -- this is the vertical join that lets a cheek 160 // reach around an eye and keeps one face as one object. 161 y = 1 162 while y < h { 163 var i: i64 = rf[y] 164 let ie: i64 = rf[y] + rc[y] 165 while i < ie { 166 var j: i64 = rf[y-1] 167 let je: i64 = rf[y-1] + rc[y-1] 168 while j < je { 169 if rs[i] <= re[j] { if rs[j] <= re[i] { 170 let ra: i64 = sl_find(pa, i) 171 let rb: i64 = sl_find(pa, j) 172 if ra != rb { if ra < rb { pa[rb] = ra } else { pa[ra] = rb } } 173 } } 174 j = j + 1 175 } 176 i = i + 1 177 } 178 y = y + 1 179 } 180 let ar: *i64 = sys_mmap(SL_MAXRUNS*8) as *i64 181 var k: i64 = 0 182 while k < nr { ar[k] = 0; k = k + 1 } 183 k = 0 184 while k < nr { let r: i64 = sl_find(pa,k); ar[r] = ar[r] + (re[k]-rs[k]+1); k = k + 1 } 185 var best: i64 = 0-1 186 var bestA: i64 = 0-1 187 k = 0 188 while k < nr { let r: i64 = sl_find(pa,k); if ar[r] > bestA { bestA = ar[r]; best = r } k = k + 1 } 189 y = 0 190 while y < h { clo[y] = 0-1; chi[y] = 0-1; y = y + 1 } 191 k = 0 192 while k < nr { 193 if sl_find(pa,k) == best { 194 let yy: i64 = rr[k] 195 if clo[yy] < 0 { clo[yy] = rs[k] } else { if rs[k] < clo[yy] { clo[yy] = rs[k] } } 196 if re[k] > chi[yy] { chi[yy] = re[k] } 197 } 198 k = k + 1 199 } 200 return bestA 201} 202func sl_scan(rgb: *u8, w: i64, h: i64, W: *i64, bands: i64) -> i64 { 203 if w <= 0 { return 0-1 } 204 if h <= 0 { return 0-2 } 205 let lo: *i64 = sys_mmap((h+8)*8) as *i64 206 let hi: *i64 = sys_mmap((h+8)*8) as *i64 207 var y: i64 = 0 208 var ytop: i64 = 0-1 209 var ybot: i64 = 0-1 210 var rows: i64 = 0 211 var bleed: i64 = 0 212 // REGISTRATION: the skin bounding box in IMAGE pixels. Bands are placed in the FIGURE's frame, which is 213 // what makes a photo comparable to a mesh regardless of crop -- but it also means a profile cannot be lined 214 // up against any OTHER seat's reading of the same photograph, because nothing here says which image rows the 215 // bands covered. Without that, a cross-seat disagreement cannot be told apart from a DEFINITIONAL mismatch, 216 // and combining the two reports inconsistency that is really just two different definitions of one word. 217 var gxlo: i64 = SL_MAGIC_1000000000 218 var gxhi: i64 = 0-1 219 // CONNECTIVITY. The scan below takes the FIRST and LAST skin pixel in a row as the figure's span, with no 220 // test that the two belong to the same object. Any skin-toned pixel elsewhere in the row -- UI chrome, a 221 // wooden block, sand, a second person -- is absorbed, and the reported width becomes the GAP BETWEEN TWO 222 // UNRELATED THINGS. edge_bleed cannot see this: it fires only when a row reaches BOTH borders, so a 223 // contaminant that stops short of the frame edge passes every existing tooth in silence. 224 // Measured before it is fixed: blo/bhi carry the LONGEST CONTIGUOUS run per row, so the span-based answer 225 // and the connected answer can be compared on the same frame instead of one replacing the other on faith. 226 // THE FIX: per-row extents now come from 2D CONNECTED COMPONENTS. The row scan below stays, but ONLY to 227 // report what was excluded -- the contamination is worth showing even once it has stopped being measured. 228 let clo: *i64 = sys_mmap((h+8)*8) as *i64 229 let chi: *i64 = sys_mmap((h+8)*8) as *i64 230 let ccA: i64 = sl_ccl(rgb, w, h, clo, chi) 231 if ccA < 0 { return ccA } 232 var multirow: i64 = 0 233 var spansum: i64 = 0 234 var bestsum: i64 = 0 235 while y < h { 236 var xlo: i64 = 0-1 237 var xhi: i64 = 0-1 238 var run: i64 = 0 239 var x: i64 = 0 240 var nrun: i64 = 0 241 var blen: i64 = 0 242 while x < w { 243 let o: i64 = (y*w + x)*3 244 if sl_is_skin(rgb[o] as i64, rgb[o+1] as i64, rgb[o+2] as i64) == 1 { 245 run = run + 1 246 if run >= SL_MINRUN { if xlo < 0 { xlo = x - run + 1 } ; xhi = x } 247 } else { 248 if run >= SL_MINRUN { 249 nrun = nrun + 1 250 if run > blen { blen = run } 251 } 252 run = 0 253 } 254 x = x + 1 255 } 256 if run >= SL_MINRUN { 257 nrun = nrun + 1 258 if run > blen { blen = run } 259 } 260 lo[y] = clo[y] 261 hi[y] = chi[y] 262 if xlo >= 0 { 263 if nrun > 1 { multirow = multirow + 1 } 264 spansum = spansum + (xhi - xlo + 1) 265 bestsum = bestsum + blen 266 } 267 if lo[y] >= 0 { 268 rows = rows + 1 269 if ytop < 0 { ytop = y } 270 ybot = y 271 if lo[y] < gxlo { gxlo = lo[y] } 272 if hi[y] > gxhi { gxhi = hi[y] } 273 // ★★★EDGE BLEED -- the measurement that tells over-firing from segmenting. A row that runs from the 274 // left border to the right border has no background left in it, and a figure with no background is 275 // not a figure, it is a classifier saying yes to everything. This is a PHYSICAL test, not a tuned 276 // one: a photograph of a person standing in a frame must show non-skin at one side or the other. 277 if xlo <= SL_EDGE { if xhi >= w-1-SL_EDGE { bleed = bleed + 1 } } 278 } 279 y = y + 1 280 } 281 if ytop < 0 { return 0-3 } 282 if ybot <= ytop { return 0-4 } 283 let fh: i64 = ybot - ytop + 1 284 var i: i64 = 0 285 while i < bands { W[i] = 0; i = i + 1 } 286 // ★BANDS ARE PLACED IN THE FIGURE'S OWN FRAME, never the image frame. A photo carries arbitrary headroom 287 // and crop, so measuring in image coordinates would make the same body score differently in a tighter crop. 288 // Normalising by FIGURE height is what makes a photo and a mesh comparable. 289 // ★★★AND THE FRAME IS GROUND-UP: band 0 is the FEET, band n-1 is the crown. That is not a preference, it is 290 // the mesh's convention, and the two MUST agree or nothing downstream means anything. Image rows count 291 // DOWNWARD from the top, so the natural loop index is upside down relative to a mesh, whose y counts UPWARD 292 // from the floor. Emitting the natural order would have put the waist landmark -- 615/1000 of stature above 293 // the GROUND, the anthropometric definition -- at 615/1000 below the CROWN, which is mid-thigh. The judge 294 // would then have measured a thigh, called it a waist, and printed a perfectly plausible number. 295 // ★LAW: TWO PROFILES THAT DISAGREE ABOUT WHICH END IS UP PRODUCE NO ERROR, ONLY WRONG ANSWERS. Fix it at the 296 // emitter so the FILE carries the convention, not at each reader where the next reader will get it wrong. 297 y = ytop 298 while y <= ybot { 299 if lo[y] >= 0 { 300 var bi: i64 = (ybot - y) * bands / fh 301 if bi < 0 { bi = 0 } 302 if bi >= bands { bi = bands - 1 } 303 // half-width, normalised by figure height so the result is dimensionless like the mesh path 304 // lo/hi are the LARGEST CONNECTED COMPONENT's extents for this row (see sl_ccl), so this is the 305 // width of one object -- not the gap between two, and not one fragment of one. 306 let hw: i64 = (hi[y] - lo[y] + 1) * SL_PREC / (2*fh) 307 if hw > W[bi] { W[bi] = hw } 308 } 309 y = y + 1 310 } 311 W[bands] = rows 312 W[bands+1] = rows * SL_MM / h 313 W[bands+2] = bleed * SL_MM / rows 314 W[bands+3] = ytop 315 W[bands+4] = ybot 316 W[bands+5] = gxlo 317 W[bands+6] = gxhi 318 // how badly the span answer differs from the connected answer, on THIS frame 319 W[bands+7] = multirow * SL_MM / rows 320 W[bands+8] = 0 321 if spansum > 0 { W[bands+8] = (spansum - bestsum) * SL_MM / spansum } 322 // ★★★AND THEN REFUSE. Reporting the number is not enough -- the caller downstream reads a profile, not a 323 // JSON field, and this organ's whole contract is that it refuses rather than inventing a body. A frame whose 324 // rows mostly run border to border has been classified, not segmented, and every band in it is the frame 325 // width rather than a body width. Measured on the licensed full-body reference: waist and hip came back 326 // EXACTLY equal at the frame's own half-width, curvature exactly zero -- a perfectly confident reading of 327 // the photograph's rectangle. That is the failure this refuses. 328 // ⚠MEASURED, NOT ASSUMED: on the synthetic fixtures bleed is 0; on the over-firing reference it is ~1000. 329 // The constant sits between two measured populations rather than being a number someone liked. 330 if W[bands+2] > SL_MAXBLEED { return 0-5 } 331 return fh 332} 333func sl_gate() -> i64 { 334 let ctr: *i64 = gv_ctr() 335 gv_head("nx_silhouette selftest -- a photograph becomes a profile, or it refuses" as *u8) 336 let w: i64 = 200 337 let h: i64 = 400 338 let rgb: *u8 = sys_mmap(w*h*3 + 64) 339 let W: *i64 = sys_mmap((SL_BANDS+16)*8) as *i64 340 // ★★T1 A KNOWN RECTANGLE READS BACK AS ITSELF. Paint a skin-coloured block 100px wide over 400px of 341 // height and the half-width must be 50/400 of figure height, in the profile's units. Calibration first: 342 // a segmenter that finds a figure but mismeasures it is worse than one that finds nothing. 343 var p: i64 = 0 344 while p < w*h*3 { rgb[p] = 20 as u8; rgb[p+1] = 90 as u8; rgb[p+2] = 40 as u8; p = p + 3 } // green field 345 var yy: i64 = 0 346 while yy < h { 347 var xx: i64 = 50 348 while xx < 150 { let o: i64 = (yy*w+xx)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; xx = xx+1 } 349 yy = yy + 1 350 } 351 let fh1: i64 = sl_scan(rgb, w, h, W, SL_BANDS) 352 let want: i64 = 100 * SL_PREC / (2*h) 353 var dd: i64 = W[SL_BANDS/2] - want 354 if dd < 0 { dd = 0 - dd } 355 var t1: i64 = 0 356 if fh1 == h { if dd <= want/50 { t1 = 1 } } 357 gv_check("T1 CALIBRATED: a 100px-wide block over the full height reads back its true half-width" as *u8, t1, ctr) 358 // ★★T2 REFUSES A BACKGROUND. A frame with no skin in it must return an error, not an empty-but-confident 359 // profile. This is the tooth that stops the organ inventing a body out of foliage. 360 var q: i64 = 0 361 while q < w*h*3 { rgb[q] = 20 as u8; rgb[q+1] = 90 as u8; rgb[q+2] = 40 as u8; q = q + 3 } 362 var t2: i64 = 0 363 if sl_scan(rgb, w, h, W, SL_BANDS) < 0 { t2 = 1 } 364 gv_check("T2 REFUSES: an all-background frame errors rather than emitting an empty profile" as *u8, t2, ctr) 365 // ★★★T3 THE SHAPE SURVIVES. An hourglass-shaped skin region must come back as an hourglass -- narrow in 366 // the middle, wide at the ends. Without this the organ could be finding the figure and flattening it. 367 var r: i64 = 0 368 while r < w*h*3 { rgb[r] = 20 as u8; rgb[r+1] = 90 as u8; rgb[r+2] = 40 as u8; r = r + 3 } 369 var y2: i64 = 0 370 while y2 < h { 371 var half: i64 = 50 372 if y2 > h/4 { if y2 < h/2 { half = 50 - 25*(y2-h/4)/(h/4) } } 373 if y2 >= h/2 { if y2 < h*3/4 { half = 25 + 25*(y2-h/2)/(h/4) } } 374 var x2: i64 = w/2 - half 375 while x2 < w/2 + half { let o: i64 = (y2*w+x2)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; x2 = x2+1 } 376 y2 = y2 + 1 377 } 378 sl_scan(rgb, w, h, W, SL_BANDS) 379 let top: i64 = W[SL_BANDS/8] 380 let mid: i64 = W[SL_BANDS/2] 381 var t3: i64 = 0 382 if mid > 0 { if mid < top*3/4 { t3 = 1 } } 383 gv_check("T3 SHAPE SURVIVES: an hourglass region reads narrow in the middle, wide at the ends" as *u8, t3, ctr) 384 // ★★T4 CROP INVARIANCE -- the property that makes a photo comparable to a mesh at all. The same figure 385 // photographed with more headroom must give the same profile, because bands are placed in the FIGURE's 386 // frame and normalised by FIGURE height. Measure in image coordinates instead and a tighter crop silently 387 // becomes a different body. 388 let h2: i64 = 600 389 let rgb2: *u8 = sys_mmap(w*h2*3 + 64) 390 var s: i64 = 0 391 while s < w*h2*3 { rgb2[s] = 20 as u8; rgb2[s+1] = 90 as u8; rgb2[s+2] = 40 as u8; s = s + 3 } 392 var y3: i64 = 100 393 while y3 < 500 { 394 var x3: i64 = 50 395 while x3 < 150 { let o: i64 = (y3*w+x3)*3; rgb2[o]=200 as u8; rgb2[o+1]=150 as u8; rgb2[o+2]=120 as u8; x3 = x3+1 } 396 y3 = y3 + 1 397 } 398 let W2: *i64 = sys_mmap((SL_BANDS+8)*8) as *i64 399 sl_scan(rgb2, w, h2, W2, SL_BANDS) 400 var d4: i64 = W2[SL_BANDS/2] - want 401 if d4 < 0 { d4 = 0 - d4 } 402 var t4: i64 = 0 403 if d4 <= want/50 { t4 = 1 } 404 gv_check("T4 CROP-INVARIANT: the same figure with extra headroom gives the same profile" as *u8, t4, ctr) 405 // ★T5 COVERAGE IS REPORTED, so a figure occupying a sliver of the frame is visible as a weak measurement 406 // rather than passing as a strong one. 407 var t5: i64 = 0 408 if W2[SL_BANDS+1] > 0 { if W2[SL_BANDS+1] < SL_MM { t5 = 1 } } 409 gv_check("T5 coverage is REPORTED -- a sliver of figure cannot pass as a full-frame measurement" as *u8, t5, ctr) 410 // ★T6 the chroma rule rejects the two things that most often fool a skin box: foliage and open water 411 var t6: i64 = 0 412 if sl_is_skin(20, 90, 40) == 0 { if sl_is_skin(40, 90, 140) == 0 { if sl_is_skin(200, 150, 120) == 1 { t6 = 1 } } } 413 gv_check("T6 foliage and water are rejected, skin is accepted -- the ordering rule earns its place" as *u8, t6, ctr) 414 // ★★★T7 WHICH END IS UP. Every fixture above this line is vertically SYMMETRIC -- a full-height rectangle, 415 // an hourglass pinched exactly at the middle, another rectangle -- so all six passed identically whether the 416 // profile ran feet-to-crown or crown-to-feet. The organ shipped inverted relative to the mesh path and SIX 417 // GREEN TEETH SAID NOTHING, because not one of them could tell the difference. 418 // ⚠THE FIXTURE IS THE TEST. A symmetric witness cannot testify about orientation, however many of them there 419 // are, and a suite of them reads exactly like a suite that works. 420 // So: a WEDGE, unmistakably wide at the image BOTTOM and narrow at the image TOP. Band 0 is defined as the 421 // FEET, so band 0 must come back WIDE. Invert the emitter and this tooth goes red immediately. 422 var u: i64 = 0 423 while u < w*h*3 { rgb[u] = 20 as u8; rgb[u+1] = 90 as u8; rgb[u+2] = 40 as u8; u = u + 3 } 424 var y4: i64 = 0 425 while y4 < h { 426 let half: i64 = 10 + 60*y4/h // 10px at the top of the image, 70px at the bottom 427 var x4: i64 = w/2 - half 428 while x4 < w/2 + half { let o: i64 = (y4*w+x4)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; x4 = x4+1 } 429 y4 = y4 + 1 430 } 431 sl_scan(rgb, w, h, W, SL_BANDS) 432 let foot_end: i64 = W[2] 433 let head_end: i64 = W[SL_BANDS-3] 434 var t7: i64 = 0 435 if foot_end > head_end*2 { t7 = 1 } 436 gv_check("T7 GROUND-UP: band 0 is the FEET -- a wedge wide at the image bottom reads wide at band 0, which is the mesh's convention and the only orientation that makes a photo and a body comparable" as *u8, t7, ctr) 437 // ★★★T8 THE DECOY THIS GATE NEVER HAD. Every fixture above paints skin on a GREEN field, which is the one 438 // background a chroma rule is guaranteed to reject -- so seven teeth said the segmenter worked, and on the 439 // first real photograph it classified the entire frame as body. It returned waist EXACTLY equal to hip and 440 // curvature EXACTLY zero: not a crash, not a warning, a confident measurement of the photograph's rectangle. 441 // ★LAW: A GATE THAT ONLY EVER SHOWS THE EASY BACKGROUND IS MEASURING THE FIXTURE, NOT THE ORGAN. The decoy 442 // has to be the thing that actually fools it in the field -- here, a warm skin-toned backdrop. 443 var v: i64 = 0 444 while v < w*h*3 { rgb[v] = 205 as u8; rgb[v+1] = 155 as u8; rgb[v+2] = 125 as u8; v = v + 3 } // beige wall 445 var y5: i64 = 0 446 while y5 < h { 447 var x5: i64 = 70 448 while x5 < 130 { let o: i64 = (y5*w+x5)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; x5 = x5+1 } 449 y5 = y5 + 1 450 } 451 var t8: i64 = 0 452 if sl_scan(rgb, w, h, W, SL_BANDS) == 0-5 { t8 = 1 } 453 gv_check("T8 REFUSES A SKIN-TONED BACKGROUND: a figure against a beige wall bleeds border to border and is refused, not measured -- the failure that seven green teeth missed on the first real photograph" as *u8, t8, ctr) 454 // T9 TWO OBJECTS IN ONE ROW -- the fixture the first eight teeth never had. Every fixture above paints 455 // exactly ONE skin object, so none of them could see the scan treating the first and last skin pixel in a 456 // row as one figure. On the first real game frame (a portrait with a tan UI swatch at the left edge) 914 457 // per-mille of figure rows held more than one object and the reported width was 435 per-mille too wide -- 458 // while edge_bleed read 0 and called that frame clean, because the contaminant stopped short of the border. 459 // LAW: A CONTROL THAT CATCHES TOTAL FAILURE IS SILENT ON PARTIAL FAILURE, and the frames it rated cleanest 460 // were the worst contaminated. 461 var m9: i64 = 0 462 while m9 < w*h*3 { rgb[m9] = 20 as u8; rgb[m9+1] = 90 as u8; rgb[m9+2] = 40 as u8; m9 = m9 + 3 } 463 var y9: i64 = 0 464 while y9 < h { 465 var xa: i64 = 10 466 while xa < 30 { let o: i64 = (y9*w+xa)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; xa = xa+1 } 467 var xb: i64 = 120 468 while xb < 180 { let o: i64 = (y9*w+xb)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; xb = xb+1 } 469 y9 = y9 + 1 470 } 471 sl_scan(rgb, w, h, W, SL_BANDS) 472 // the FIGURE is the 60px block; the 20px decoy sits at the left, so first-to-last reports 170px. 473 // T9 asserts CONTAMINATION IS DETECTED, not that it is removed. Asserting the removed-width here would 474 // have locked in the longest-run remedy that the A/B above proved wrong (one cheek instead of one face). 475 // A tooth must assert the property the organ actually promises: this frame is reported as contaminated. 476 var t9: i64 = 0 477 if W[SL_BANDS+7] > SL_MM/2 { t9 = 1 } 478 gv_check("T9 CONTAMINATION IS VISIBLE: two skin objects in one row (a tan UI swatch beside the figure) are REPORTED via multirun_rows_permil, on a frame where edge_bleed reads 0 and every other tooth is silent -- a control that catches total failure is blind to partial failure" as *u8, t9, ctr) 479 // T10 CONTAMINATION IS REMOVED, not merely reported -- same fixture, now asserting the WIDTH. The figure is 480 // the 60px block; first-to-last would report 170px because the 20px decoy sits at the left edge. Measured on 481 // the real portrait this fixture stands for: the shoulder bands read 673px before and 328px after. 482 let want10: i64 = 60 * SL_PREC / (2*h) 483 var d10: i64 = W[SL_BANDS/2] - want10 484 if d10 < 0 { d10 = 0 - d10 } 485 var t10: i64 = 0 486 if d10 <= want10/20 { t10 = 1 } 487 gv_check("T10 CONTAMINATION IS REMOVED: with a tan UI swatch beside her, the profile reports the FIGURE's own width and not the gap to the decoy -- the largest connected component is the subject" as *u8, t10, ctr) 488 // T11 AN INTERNAL HOLE MUST NOT SPLIT THE FIGURE -- the fixture NONE of T1..T10 had. Every earlier fixture is 489 // a SOLID blob, so not one of them could see a row-local rule fragmenting a real subject: a horizontal scan 490 // across a face crosses cheek, sclera, bridge, sclera, cheek. Measured on a real portrait, the longest-run 491 // rule returned ONE CHEEK -- 47px against a 125px face -- and passed every solid-blob tooth while doing it. 492 // 2D connectivity is what makes an eye a hole INSIDE the face instead of a wall between two objects. 493 var m11: i64 = 0 494 while m11 < w*h*3 { rgb[m11] = 20 as u8; rgb[m11+1] = 90 as u8; rgb[m11+2] = 40 as u8; m11 = m11 + 3 } 495 var ya: i64 = 0 496 while ya < h { 497 var xa: i64 = 60 498 while xa < 140 { let o: i64 = (ya*w+xa)*3; rgb[o]=200 as u8; rgb[o+1]=150 as u8; rgb[o+2]=120 as u8; xa = xa+1 } 499 ya = ya + 1 500 } 501 var yh: i64 = h/3 502 while yh < h/3 + 20 { 503 var xh: i64 = 75 504 while xh < 90 { let o: i64 = (yh*w+xh)*3; rgb[o]=20 as u8; rgb[o+1]=90 as u8; rgb[o+2]=40 as u8; xh = xh+1 } 505 var xk: i64 = 110 506 while xk < 125 { let o: i64 = (yh*w+xk)*3; rgb[o]=20 as u8; rgb[o+1]=90 as u8; rgb[o+2]=40 as u8; xk = xk+1 } 507 yh = yh + 1 508 } 509 sl_scan(rgb, w, h, W, SL_BANDS) 510 let want11: i64 = 80 * SL_PREC / (2*h) 511 var mn11: i64 = SL_MAGIC_1000000000 512 var b11: i64 = 1 513 while b11 < SL_BANDS-1 { if W[b11] < mn11 { mn11 = W[b11] } b11 = b11 + 1 } 514 var t11: i64 = 0 515 if mn11 >= want11 - want11/10 { t11 = 1 } 516 gv_check("T11 AN INTERNAL HOLE DOES NOT SPLIT THE FIGURE: a blob with two non-skin eyes punched through it still measures its FULL width in EVERY band -- a row-local rule reports the widest fragment instead, and every solid-blob fixture is blind to the difference" as *u8, t11, ctr) 517 return gv_verdict("SILHOUETTE-GATE" as *u8, ctr, "photo to profile; figure-framed, ground-up; refuses a background; EXCLUDES contaminating objects by 2D connected components and still reports what it excluded" as *u8) 518} 519func main(argc: i64, argv: *i64) -> i64 { 520 if argc >= 2 { 521 if sl_streq(argv[1] as *u8, "selftest" as *u8) == 1 { return sl_gate() } 522 if sl_streq(argv[1] as *u8, "profile" as *u8) == 1 { 523 if argc < 4 { sl_puts("{\x22error\x22:\x22usage: nx_silhouette profile <image> <out.prof> [bands]\x22}\n" as *u8); return 2 } 524 var bands: i64 = SL_BANDS 525 if argc > 4 { bands = sl_atoi(argv[4] as *u8) } 526 let szp: *i64 = sys_mmap(16) as *i64 527 let raw: *u8 = sys_read_file(argv[2] as *u8, szp) 528 if (raw as i64) == 0 { sl_puts("{\x22error\x22:\x22image unreadable\x22}\n" as *u8); return 3 } 529 let wh: *i64 = sys_mmap(32) as *i64 530 let rgb: *u8 = nx_img_bytes_to_rgb(raw, szp[0], wh) 531 if (rgb as i64) == 0 { sl_puts("{\x22error\x22:\x22image not decodable to RGB\x22}\n" as *u8); return 4 } 532 let W: *i64 = sys_mmap((bands+16)*8) as *i64 533 let fh: i64 = sl_scan(rgb, wh[0], wh[1], W, bands) 534 if fh < 0 { 535 sl_puts("{\x22organ\x22:\x22nx_silhouette\x22,\x22verdict\x22:\x22REFUSED\x22,\x22rc\x22:" as *u8); sl_pn(fh) 536 if fh == 0-5 { 537 sl_puts(",\x22why\x22:\x22EDGE BLEED -- most rows run border to border, so there is no background left to segment against. The skin rule is matching the scene, not the subject: a warm wall, sand, wood or a skin-toned backdrop all pass a chroma test. Any profile from this frame would be the FRAME's width in every band, which reads as a perfectly confident body with zero curvature.\x22" as *u8) 538 sl_puts(",\x22fix\x22:\x22use a frame with visible non-skin background at one side, or segment with something stronger than a chroma rule\x22}\n" as *u8) 539 } else { 540 sl_puts(",\x22why\x22:\x22no figure found -- the organ reports nothing rather than a profile it did not measure\x22}\n" as *u8) 541 } 542 return 5 543 } 544 // write the profile for nx_curvebench to measure. ONE curvature implementation, two sources. 545 let buf: *u8 = sys_mmap(bands*16 + 256) 546 let pos: *i64 = sys_mmap(16) as *i64 547 pos[0] = 0 548 var i: i64 = 0 549 while i < bands { sl_wint(buf, pos, W[i], 10); i = i + 1 } 550 let fd: i64 = sys_openat_wr(argv[3] as *u8, SL_MODE) 551 if fd < 0 { sl_puts("{\x22error\x22:\x22cannot write profile\x22}\n" as *u8); return 6 } 552 sys_write(fd, buf, pos[0]) 553 sys_close(fd) 554 sl_puts("{\x22organ\x22:\x22nx_silhouette\x22,\x22v\x22:1" as *u8) 555 sl_puts(",\x22width\x22:" as *u8); sl_pn(wh[0]) 556 sl_puts(",\x22height\x22:" as *u8); sl_pn(wh[1]) 557 sl_puts(",\x22figure_height_px\x22:" as *u8); sl_pn(fh) 558 sl_puts(",\x22figure_rows\x22:" as *u8); sl_pn(W[bands]) 559 sl_puts(",\x22coverage_permil\x22:" as *u8); sl_pn(W[bands+1]) 560 sl_puts(",\x22edge_bleed_permil\x22:" as *u8); sl_pn(W[bands+2]) 561 sl_puts(",\x22bands\x22:" as *u8); sl_pn(bands) 562 // the skin bbox in IMAGE pixels, so another seat can line ITS reading up against these bands 563 // instead of guessing which rows they covered. Band b spans image rows measured from skin_y1 564 // upward: y = skin_y1 - b*(skin_y1-skin_y0+1)/bands. A seat that cannot be registered cannot be 565 // combined, and combining it anyway is how a definitional gap gets reported as a disagreement. 566 sl_puts(",\x22skin_y0\x22:" as *u8); sl_pn(W[bands+3]) 567 sl_puts(",\x22skin_y1\x22:" as *u8); sl_pn(W[bands+4]) 568 sl_puts(",\x22skin_x0\x22:" as *u8); sl_pn(W[bands+5]) 569 sl_puts(",\x22skin_x1\x22:" as *u8); sl_pn(W[bands+6]) 570 // CONTAMINATION CONTROL: what fraction of figure rows contain more than one skin object, and how 571 // much wider the first-to-last span is than the largest single connected run. Both zero on a clean 572 // frame; a large second number means the profile is the distance between unrelated objects. 573 sl_puts(",\x22multirun_rows_permil\x22:" as *u8); sl_pn(W[bands+7]) 574 sl_puts(",\x22span_over_connected_permil\x22:" as *u8); sl_pn(W[bands+8]) 575 sl_puts(",\x22method\x22:\x22skin classified in CHROMA (Cb/Cr locus plus the R>G>B ordering rule), so a figure lit half in sun and half in shade stays one figure where a brightness threshold would cut it at the shadow line. Bands are placed in the FIGURE's frame and normalised by FIGURE height, which is what makes a photo comparable to a mesh regardless of crop.\x22" as *u8) 576 sl_puts(",\x22honest_scope\x22:\x22this is not general matting. It works on a figure against a NON-SKIN background and fails on skin-toned backdrops, sand and tanned wood -- that limit was written here as prose from the first version, and prose did not stop 74 photographs being turned into 74 confident numbers. It is now a CONTROL: edge_bleed_permil counts rows running border to border, and the organ REFUSES above the measured line instead of reporting the frame's own width as a body. A caveat a caller can ignore is not a limit.\x22}\n" as *u8) 577 return 0 578 } 579 } 580 sl_puts("{\x22organ\x22:\x22nx_silhouette\x22,\x22usage\x22:\x22nx_silhouette profile <image> <out.prof> [bands] | nx_silhouette selftest\x22}\n" as *u8) 581 return 0 582}