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}