code wiki / (root) / nx_natstat.nx

nx_natstat.nx source

↩ module page · 472 lines · 19321 B

1// nx_natstat.nx -- NATURAL-IMAGE-STATISTICS photoreal metric = the REVERSE AI JUDGE. Real photographs have a 2// signature our old critic missed: DETAIL AT EVERY SCALE (a ~1/f amplitude spectrum -> roughly EQUAL detail 3// energy per octave). Clay/CG fails it two ways: too SMOOTH (fine + mid octaves empty) or GRAIN-ONLY (a spike 4// at the finest octave, smooth beneath). We measure the multi-scale detail profile + its balance (+ gradient 5// heavy-tail + colour spread) and return a graded PHOTOREAL LEVEL 0..1000. Uses: 6// (a) REVERSE JUDGE: maximise this to push Z-Image toward more-real output for games/VR (evaluator-in-reverse); 7// (b) "USE-AS-PHOTOREAL-TO-LEVEL": even a known-AI image is usable as photoreal up to the level it scores. 8// Integer, deterministic, no FFT (Laplacian-pyramid octave energies). license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_vecmath.nx" 11const K_MAGIC_65536: i64 = 65536 12const K_MAGIC_3200: i64 = 3200 13const K_MAGIC_6500: i64 = 6500 14 15func ns_lum(g: i64) -> i64 { return ((g & 255) + ((g >> 8) & 255) + ((g >> 16) & 255)) / 3 } 16func ns_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 17func ns_isqrt(v: i64) -> i64 { return vm_isqrt(v) } 18 19// ★N1 NSS: MSCN (Mean-Subtracted Contrast-Normalized) coefficients + the GGD RATIO ρ=(E|x|)²/E[x²] (the core 20// BRISQUE/NIQE feature). For a NATURAL image the MSCN coefficients are ~unit-Gaussian -> ρ ≈ 0.637 (=2/π); 21// synthetic/CG deviates. Local mean+std over a 5x5 window; returns ρ in PER-MILLE (natural ~637). 22func ns_mscn_rho(fb: *i64, w: i64, h: i64) -> i64 { 23 let R: i64 = 2 24 var sum_abs: i64 = 0 25 var sum_sq: i64 = 0 26 var n: i64 = 0 27 var y: i64 = R 28 while y < h - R { 29 var x: i64 = R 30 while x < w - R { 31 var m: i64 = 0 32 var m2: i64 = 0 33 var dy: i64 = 0 - R 34 while dy <= R { 35 var dx: i64 = 0 - R 36 while dx <= R { 37 let v: i64 = ns_lum(fb[(y + dy) * w + (x + dx)]) 38 m = m + v 39 m2 = m2 + v * v 40 dx = dx + 1 41 } 42 dy = dy + 1 43 } 44 let mean: i64 = m / 25 45 var vr: i64 = m2 / 25 - mean * mean 46 if vr < 0 { vr = 0 } 47 let sd: i64 = ns_isqrt(vr) 48 let cval: i64 = ns_lum(fb[y * w + x]) 49 let mscn: i64 = (cval - mean) * 64 / (sd + 3) // fx64, C=3 stability 50 sum_abs = sum_abs + ns_abs(mscn) 51 sum_sq = sum_sq + mscn * mscn / 64 // Σ mscn² in fx64 52 n = n + 1 53 x = x + 1 54 } 55 y = y + 1 56 } 57 if n < 1 { return 0 } 58 if sum_sq < 1 { return 0 } 59 return sum_abs * sum_abs * 1000 / n / sum_sq / 64 // ρ per-mille (natural ~637) 60} 61// ★BRISQUE pairwise features: fill an MSCN buffer once, then read ρ + PAIRWISE-PRODUCT means (how adjacent MSCN 62// coeffs correlate -- the local-structure signal that ρ alone misses; all MSCN-derived => content-invariant). 63func ns_mscn_fill(fb: *i64, w: i64, h: i64, mbuf: *i64) -> i64 { 64 var i: i64 = 0 65 while i < w * h { mbuf[i] = 0; i = i + 1 } 66 let R: i64 = 2 67 var y: i64 = R 68 while y < h - R { 69 var x: i64 = R 70 while x < w - R { 71 var m: i64 = 0 72 var m2: i64 = 0 73 var dy: i64 = 0 - R 74 while dy <= R { 75 var dx: i64 = 0 - R 76 while dx <= R { 77 let v: i64 = ns_lum(fb[(y + dy) * w + (x + dx)]) 78 m = m + v 79 m2 = m2 + v * v 80 dx = dx + 1 81 } 82 dy = dy + 1 83 } 84 let mean: i64 = m / 25 85 var vr: i64 = m2 / 25 - mean * mean 86 if vr < 0 { vr = 0 } 87 mbuf[y * w + x] = (ns_lum(fb[y * w + x]) - mean) * 64 / (ns_isqrt(vr) + 3) // MSCN fx64 88 x = x + 1 89 } 90 y = y + 1 91 } 92 return 0 93} 94func ns_mscn_rho_buf(mbuf: *i64, w: i64, h: i64) -> i64 { 95 var sa: i64 = 0 96 var sq: i64 = 0 97 var n: i64 = 0 98 var y: i64 = 2 99 while y < h - 2 { 100 var x: i64 = 2 101 while x < w - 2 { 102 let m: i64 = mbuf[y * w + x] 103 sa = sa + ns_abs(m) 104 sq = sq + m * m / 64 105 n = n + 1 106 x = x + 1 107 } 108 y = y + 1 109 } 110 if n < 1 { return 0 } 111 if sq < 1 { return 0 } 112 return sa * sa * 1000 / n / sq / 64 113} 114// mean pairwise product of MSCN neighbours in direction (dx,dy), fx64. Natural images -> characteristically 115// negative (adjacent normalised coeffs anti-correlate) with a specific magnitude. 116func ns_mscn_pair(mbuf: *i64, w: i64, h: i64, dx: i64, dy: i64) -> i64 { 117 var s: i64 = 0 118 var n: i64 = 0 119 var y: i64 = 2 120 while y < h - 3 { 121 var x: i64 = 2 122 while x < w - 3 { 123 s = s + mbuf[y * w + x] * mbuf[(y + dy) * w + (x + dx)] / 64 124 n = n + 1 125 x = x + 1 126 } 127 y = y + 1 128 } 129 if n < 1 { return 0 } 130 return s / n 131} 132 133// mean |Laplacian| (centre - avg 4-neighbours) over the interior, in luminance, x100 for precision = octave detail energy 134func ns_detail(fb: *i64, w: i64, h: i64) -> i64 { 135 if w < 3 { return 0 } 136 if h < 3 { return 0 } 137 var s: i64 = 0 138 var n: i64 = 0 139 var y: i64 = 1 140 while y < h - 1 { 141 var x: i64 = 1 142 while x < w - 1 { 143 let c: i64 = ns_lum(fb[y * w + x]) 144 let nb: i64 = (ns_lum(fb[y * w + x - 1]) + ns_lum(fb[y * w + x + 1]) + ns_lum(fb[(y - 1) * w + x]) + ns_lum(fb[(y + 1) * w + x])) / 4 145 s = s + ns_abs(c - nb) 146 n = n + 1 147 x = x + 1 148 } 149 y = y + 1 150 } 151 if n < 1 { n = 1 } 152 return s * 100 / n 153} 154// 5x5 local std (activity) at (x,y) -- used for NIQE-style patch/subject selection. 155func ns_local_std(fb: *i64, w: i64, x: i64, y: i64) -> i64 { 156 var m: i64 = 0 157 var m2: i64 = 0 158 var dy: i64 = 0 - 2 159 while dy <= 2 { 160 var dx: i64 = 0 - 2 161 while dx <= 2 { 162 let v: i64 = ns_lum(fb[(y + dy) * w + (x + dx)]) 163 m = m + v 164 m2 = m2 + v * v 165 dx = dx + 1 166 } 167 dy = dy + 1 168 } 169 let mean: i64 = m / 25 170 var vr: i64 = m2 / 25 - mean * mean 171 if vr < 0 { vr = 0 } 172 return ns_isqrt(vr) 173} 174// ★AXIS 3: gradient KURTOSIS over ACTIVE (subject) pixels only -- NIQE-style patch selection excludes the flat 175// background/sky so the statistic reflects our SURFACE, not scene composition. Natural photos ~ kurtosis 7-32 176// (×100 = 700-3200); an over-smooth blob w/ sparse hard edges spikes FAR higher (flat regions + rare big edges). 177func ns_grad_kurt(fb: *i64, w: i64, h: i64) -> i64 { 178 var peak: i64 = 0 179 var y: i64 = 2 180 while y < h - 2 { 181 var x: i64 = 2 182 while x < w - 2 { 183 let s: i64 = ns_local_std(fb, w, x, y) 184 if s > peak { peak = s } 185 x = x + 3 186 } 187 y = y + 3 188 } 189 let thresh: i64 = peak / 10 // active = >=10% of peak local activity (skips flat bg) 190 var sg2: i64 = 0 191 var sg4: i64 = 0 192 var n: i64 = 0 193 y = 2 194 while y < h - 2 { 195 var x: i64 = 2 196 while x < w - 2 { 197 if ns_local_std(fb, w, x, y) > thresh { 198 let gx: i64 = ns_lum(fb[y * w + x + 1]) - ns_lum(fb[y * w + x - 1]) 199 let gy: i64 = ns_lum(fb[(y + 1) * w + x]) - ns_lum(fb[(y - 1) * w + x]) 200 let g: i64 = (ns_abs(gx) + ns_abs(gy)) / 4 201 let g2: i64 = g * g 202 sg2 = sg2 + g2 203 sg4 = sg4 + g2 * g2 204 n = n + 1 205 } 206 x = x + 1 207 } 208 y = y + 1 209 } 210 if n < 1 { return 0 } 211 let mg2: i64 = sg2 / n 212 let mg4: i64 = sg4 / n 213 if mg2 < 1 { return 0 } 214 return mg4 * 100 / (mg2 * mg2) 215} 216// ★AXIS 4: COLOURFULNESS (Hasler-Süsstrunk) -- natural photos sit in a characteristic colour-spread range 217// (not grey, not over-saturated). Returns colourfulness in the 0..~110 luminance scale. 218func ns_colorful(fb: *i64, w: i64, h: i64) -> i64 { 219 var srg: i64 = 0 220 var syb: i64 = 0 221 var srg2: i64 = 0 222 var syb2: i64 = 0 223 var n: i64 = 0 224 var i: i64 = 0 225 let tot: i64 = w * h 226 while i < tot { 227 let g: i64 = fb[i] 228 let rr: i64 = g & 255 229 let gg: i64 = (g >> 8) & 255 230 let bb: i64 = (g >> 16) & 255 231 let rg: i64 = rr - gg 232 let yb: i64 = (rr + gg) / 2 - bb 233 srg = srg + rg 234 syb = syb + yb 235 srg2 = srg2 + rg * rg 236 syb2 = syb2 + yb * yb 237 n = n + 1 238 i = i + 1 239 } 240 if n < 1 { return 0 } 241 let mrg: i64 = srg / n 242 let myb: i64 = syb / n 243 var vrg: i64 = srg2 / n - mrg * mrg 244 var vyb: i64 = syb2 / n - myb * myb 245 if vrg < 0 { vrg = 0 } 246 if vyb < 0 { vyb = 0 } 247 let sd: i64 = ns_isqrt(vrg + vyb) 248 let mm: i64 = ns_isqrt(mrg * mrg + myb * myb) 249 return sd + mm * 3 / 10 250} 251// ★AXIS 5: AI-FINGERPRINT -- generator upsampling leaves PERIODIC high-freq artifacts. Autocorrelate the 252// horizontal high-pass residual at lags 2 & 4; a real photo's residual decorrelates (~0), AI/grid spikes it. 253// Returns the mean |autocorrelation| at lag 2&4, per-mille (LOW = clean/real, HIGH = periodic artifact). 254func ns_ai_periodicity(fb: *i64, w: i64, h: i64) -> i64 { 255 var s0: i64 = 0 256 var s2: i64 = 0 257 var s4: i64 = 0 258 var y: i64 = 1 259 while y < h - 1 { 260 var x: i64 = 2 261 while x < w - 6 { 262 let hp0: i64 = ns_lum(fb[y * w + x]) - (ns_lum(fb[y * w + x - 1]) + ns_lum(fb[y * w + x + 1])) / 2 263 let hp2: i64 = ns_lum(fb[y * w + x + 2]) - (ns_lum(fb[y * w + x + 1]) + ns_lum(fb[y * w + x + 3])) / 2 264 let hp4: i64 = ns_lum(fb[y * w + x + 4]) - (ns_lum(fb[y * w + x + 3]) + ns_lum(fb[y * w + x + 5])) / 2 265 s0 = s0 + hp0 * hp0 266 s2 = s2 + hp0 * hp2 267 s4 = s4 + hp0 * hp4 268 x = x + 1 269 } 270 y = y + 1 271 } 272 if s0 < 1 { return 0 } 273 return (ns_abs(s2) * 1000 / s0 + ns_abs(s4) * 1000 / s0) / 2 274} 275// box-downsample by 2 into out (nw=w/2 x nh=h/2) 276func ns_down2(fb: *i64, w: i64, h: i64, out: *i64) -> i64 { 277 let nw: i64 = w / 2 278 let nh: i64 = h / 2 279 var y: i64 = 0 280 while y < nh { 281 var x: i64 = 0 282 while x < nw { 283 let a: i64 = fb[(y * 2) * w + x * 2] 284 let b: i64 = fb[(y * 2) * w + x * 2 + 1] 285 let c: i64 = fb[(y * 2 + 1) * w + x * 2] 286 let d: i64 = fb[(y * 2 + 1) * w + x * 2 + 1] 287 let rr: i64 = ((a & 255) + (b & 255) + (c & 255) + (d & 255)) / 4 288 let gg: i64 = (((a >> 8) & 255) + ((b >> 8) & 255) + ((c >> 8) & 255) + ((d >> 8) & 255)) / 4 289 let bb: i64 = (((a >> 16) & 255) + ((b >> 16) & 255) + ((c >> 16) & 255) + ((d >> 16) & 255)) / 4 290 out[y * nw + x] = rr + gg * 256 + bb * K_MAGIC_65536 291 x = x + 1 292 } 293 y = y + 1 294 } 295 return 0 296} 297// raw scale-invariance feature = the octave-ratio SPREAD (0 = perfect power law; large = CG). For corpus fitting. 298func ns_scale_spread(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64) -> i64 { 299 let e0: i64 = ns_detail(fb, w, h) 300 ns_down2(fb, w, h, buf1); let e1: i64 = ns_detail(buf1, w / 2, h / 2) 301 ns_down2(buf1, w / 2, h / 2, buf2); let e2: i64 = ns_detail(buf2, w / 4, h / 4) 302 ns_down2(buf2, w / 4, h / 4, buf1); let e3: i64 = ns_detail(buf1, w / 8, h / 8) 303 var d1: i64 = e1 304 var d2: i64 = e2 305 var d3: i64 = e3 306 if d1 < 1 { d1 = 1 } 307 if d2 < 1 { d2 = 1 } 308 if d3 < 1 { d3 = 1 } 309 let r1: i64 = e0 * 256 / d1 310 let r2: i64 = e1 * 256 / d2 311 let r3: i64 = e2 * 256 / d3 312 var mn: i64 = r1 313 var mx: i64 = r1 314 if r2 < mn { mn = r2 } 315 if r2 > mx { mx = r2 } 316 if r3 < mn { mn = r3 } 317 if r3 > mx { mx = r3 } 318 return mx - mn 319} 320// 1000 inside the natural band [lo,hi]; linear falloff over `edge` beyond either side. 321func ns_band(v: i64, lo: i64, hi: i64, edge: i64) -> i64 { 322 var d: i64 = 0 323 if v < lo { d = lo - v } 324 if v > hi { d = v - hi } 325 if d == 0 { return 1000 } 326 var s: i64 = 1000 - d * 1000 / edge 327 if s < 0 { s = 0 } 328 return s 329} 330// ★COMPREHENSIVE "is this photoreal?" -- fills out[0..4] with PER-AXIS scores (0..1000) so a graphics engine 331// can SEE which axes it fails (= its gaps), and returns the weighted OVERALL level. AI-fingerprint is axis 4, 332// ONE part of the whole, not the whole. out MUST hold >=5 i64. buf1/buf2 = scratch >= w*h*8 each. 333// axis0 SCALE-INVARIANCE (power-law spectrum) · axis1 MSCN GGD-ratio (local NSS) · axis2 GRADIENT-KURTOSIS 334// (edge heavy-tail; too-high = over-smooth CG) · axis3 COLOUR-NATURALNESS · axis4 AI-FINGERPRINT (freq artifact) 335func ns_assess(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64, out: *i64) -> i64 { 336 // --- axis0: scale-invariance via the octave-ratio power law --- 337 let e0: i64 = ns_detail(fb, w, h) 338 ns_down2(fb, w, h, buf1); let e1: i64 = ns_detail(buf1, w / 2, h / 2) 339 ns_down2(buf1, w / 2, h / 2, buf2); let e2: i64 = ns_detail(buf2, w / 4, h / 4) 340 ns_down2(buf2, w / 4, h / 4, buf1); let e3: i64 = ns_detail(buf1, w / 8, h / 8) 341 var a0: i64 = 0 342 if e3 >= 40 { 343 var d1: i64 = e1 344 var d2: i64 = e2 345 var d3: i64 = e3 346 if d1 < 1 { d1 = 1 } 347 if d2 < 1 { d2 = 1 } 348 if d3 < 1 { d3 = 1 } 349 let r1: i64 = e0 * 256 / d1 350 let r2: i64 = e1 * 256 / d2 351 let r3: i64 = e2 * 256 / d3 352 var mn: i64 = r1 353 var mx: i64 = r1 354 if r2 < mn { mn = r2 } 355 if r2 > mx { mx = r2 } 356 if r3 < mn { mn = r3 } 357 if r3 > mx { mx = r3 } 358 a0 = 1000 - (mx - mn) * 20 359 if a0 < 0 { a0 = 0 } 360 } 361 // --- axis1: MSCN FAMILY (BRISQUE) = ρ + horizontal/vertical pairwise-product means, all CORPUS-FIT + 362 // content-invariant. MIN => every MSCN statistic must be natural. Centers ρ=604 ph=18 pv=10 (12 real photos). 363 // Reuse buf2 (pyramid already done with it) as the MSCN coefficient buffer. 364 ns_mscn_fill(fb, w, h, buf2) 365 let rho: i64 = ns_mscn_rho_buf(buf2, w, h) 366 let ph: i64 = ns_mscn_pair(buf2, w, h, 1, 0) 367 let pv: i64 = ns_mscn_pair(buf2, w, h, 0, 1) 368 let pd1: i64 = ns_mscn_pair(buf2, w, h, 1, 1) 369 let pd2: i64 = ns_mscn_pair(buf2, w, h, 1, 0 - 1) 370 var s_rho: i64 = 1000 - ns_abs(rho - 604) * 14 / 10 371 var s_ph: i64 = 1000 - ns_abs(ph - 18) * 1000 / 40 372 var s_pv: i64 = 1000 - ns_abs(pv - 10) * 1000 / 35 373 var s_d1: i64 = 1000 - ns_abs(pd1 - 3) * 1000 / 30 // diagonals: near-zero + noisy -> GENTLE bands 374 var s_d2: i64 = 1000 - ns_abs(pd2 - 2) * 1000 / 30 375 if s_rho < 0 { s_rho = 0 } 376 if s_ph < 0 { s_ph = 0 } 377 if s_pv < 0 { s_pv = 0 } 378 if s_d1 < 0 { s_d1 = 0 } 379 if s_d2 < 0 { s_d2 = 0 } 380 var a1: i64 = s_rho 381 if s_ph < a1 { a1 = s_ph } 382 if s_pv < a1 { a1 = s_pv } 383 if s_d1 < a1 { a1 = s_d1 } 384 if s_d2 < a1 { a1 = s_d2 } 385 // --- axis2: gradient kurtosis in the natural band (too-high = over-smooth blob CG = OUR typical gap) --- 386 let a2: i64 = ns_band(ns_grad_kurt(fb, w, h), 700, K_MAGIC_3200, K_MAGIC_6500) 387 // --- axis3: colour naturalness -- GREY bites (tight low edge) but VIVID real photos pass (corpus has colour 388 // up to 96); only EXTREME oversaturation (>100, synthetic bars) bites on the high side --- 389 let cf: i64 = ns_colorful(fb, w, h) 390 var a3: i64 = 1000 391 if cf < 12 { a3 = ns_band(cf, 12, 58, 16) } 392 if cf > 100 { a3 = 1000 - (cf - 100) * 1000 / 40 } 393 if a3 < 0 { a3 = 0 } 394 // --- axis4: AI-fingerprint (periodic freq artifact; low=clean) --- 395 let a4: i64 = ns_band(ns_ai_periodicity(fb, w, h), 0, 130, 320) 396 out[0] = a0 397 out[1] = a1 398 out[2] = a2 399 out[3] = a3 400 out[4] = a4 401 // ★realism LEVEL = MIN of the CONTENT-INVARIANT axes {MSCN(1), colour(3), ai-fp(4)}. scale-invariance(0) + 402 // raw-kurtosis(2) are CONTENT-CONFOUNDED (busy scene vs smooth portrait -- corpus proved they crater real 403 // busy photos) so they stay in the gap-map (out[]) but are EXCLUDED from realism. MIN = you are only as 404 // photoreal as your WORST true-realism statistic -> no flattery (our clay's MSCN=408 tanks it, colour/ai 405 // can't rescue it), and real photos (all three axes natural) stay high. 406 var lvl: i64 = out[1] 407 if out[3] < lvl { lvl = out[3] } 408 if out[4] < lvl { lvl = out[4] } 409 return lvl 410} 411// thin wrapper: the single graded PHOTOREAL LEVEL 0..1000. 412func ns_photoreal(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64) -> i64 { 413 let out: *i64 = sys_mmap(64) as *i64 414 return ns_assess(fb, w, h, buf1, buf2, out) 415} 416// ★HARDENING (2026-07-23 red-team): lag-1 spatial autocorrelation of luminance, per-mille. Natural images are 417// locally COHERENT (neighbours correlated: smooth regions + structured edges) ~high; white NOISE ~0. This is the 418// axis MSCN/detail miss -- pure noise maxes MSCN but has ZERO coherence, so without this the grader rates noise 419// SEMI-REAL. Averages horizontal + vertical lag-1 correlation over the interior. 420func ns_coherence(fb: *i64, w: i64, h: i64) -> i64 { 421 let np: i64 = w*h 422 var sum: i64 = 0 423 var i: i64 = 0 424 while i < np { sum = sum + ns_lum(fb[i]); i = i + 1 } 425 let mu: i64 = sum / np 426 var num: i64 = 0 427 var den: i64 = 0 428 var y: i64 = 0 429 while y < h - 1 { 430 var x: i64 = 0 431 while x < w - 1 { 432 let c: i64 = ns_lum(fb[y*w+x]) - mu 433 let rt: i64 = ns_lum(fb[y*w+x+1]) - mu 434 let dn: i64 = ns_lum(fb[(y+1)*w+x]) - mu 435 num = num + c*rt + c*dn 436 den = den + c*c*2 437 x = x + 1 438 } 439 y = y + 1 440 } 441 if den < 1 { return 0 } 442 var r: i64 = num*1000/den 443 if r < 0 { r = 0 } 444 if r > 1000 { r = 1000 } 445 return r 446} 447// ★HARDENED assess (2026-07-23): the coherence-GATED photoreal level. ns_assess alone rates pure noise 448// SEMI-REAL (475) because MSCN maxes on noise; multiply by a coherence knee so an incoherent image CANNOT score 449// high. KNEE=900 = the lower edge of the MEASURED genuine-content coherence cluster (real photos + real renders 450// all 970-998; pure noise 0; a real photo wrecked with heavy noise 606) -> data-derived, not a dialled constant. 451// out[5]=coherence is written for transparency. Genuine content (coh>=900) is UNCHANGED; noise collapses to CLAY. 452func ns_assess2(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64, out: *i64) -> i64 { 453 let base: i64 = ns_assess(fb, w, h, buf1, buf2, out) 454 let coh: i64 = ns_coherence(fb, w, h) 455 out[5] = coh 456 var lvl: i64 = base 457 if coh < 900 { lvl = base * coh / 900 } 458 return lvl 459} 460func ns_verdict(level: i64) -> *u8 { 461 if level >= 650 { return "PHOTOREAL-GRADE" as *u8 } 462 if level >= 380 { return "SEMI-REAL" as *u8 } 463 if level >= 180 { return "STYLIZED-CG" as *u8 } 464 return "CLAY" as *u8 465} 466func ns_axisname(i: i64) -> *u8 { 467 if i == 0 { return "scale-invariance (multi-scale power law) -- add texture at ALL scales" as *u8 } 468 if i == 1 { return "MSCN GGD-ratio (local contrast statistics)" as *u8 } 469 if i == 2 { return "gradient-kurtosis -- too smooth: flat blobs + sparse edges, need pervasive micro-texture" as *u8 } 470 if i == 3 { return "colour-naturalness (grey / over-saturated)" as *u8 } 471 return "ai-fingerprint (periodic generation artifact)" as *u8 472}