code wiki / (root) / nx_likeness_lib.nx

nx_likeness_lib.nx source

↩ module page · 1114 lines · 53971 B

1// nx_likeness_lib.nx -- THE IMAGE-LEVEL LIKENESS REFEREE (aesthetictwin AT45, 2026-09-18). The board had no ruler that reads a 2// RENDER against the PHOTOGRAPHS of the person it claims to be, so a render the operator called an ugly ventriloquist dummy was 3// published beside its reference with every gate green. This lib is that ruler, and every layer of the feedback loop above it 4// (fit, morph, texture, hair) is judged by it. It COMPOSES the estate's incumbents and re-implements none of them: 5// nx_photomark_lib decodes a JPEG or PNG through one door (pm_decode_file), places the four canthi and the five mouth 6// landmarks (pm_landmarks_cascade, pm_landmarks), measures the roll-corrected canthal tilts, and parses 7// the OUTSIDE oracle rows (pm_field, pm_span_int) the intake lane already banks per reference still 8// nx_visdesc the grayscale edge-orientation layout descriptor (80 integers) and its L1 distance -- the STRUCTURE axis 9// nx_cosine_similarity the signed Q10 cosine over the same two vectors -- structure read a second way 10// nx_skinsample_lib the skin chroma rule and the linear-light mean converted to CIE L*a*b* -- the TONE axis 11// THE SHAPE OF THE VERDICT. Two images are ALIGNED into one canvas by the similarity that maps each face's two eye centres onto 12// the canvas's two eye centres (the same normalisation on both sides, so a roll or a scale in either still is undone before 13// anything is compared), then SEVEN AXES are measured between the aligned pair and each is published on its own: 14// geom_nme_permil mean landmark distance in the canvas, permil of the canvas eye span (the four canthi, plus the five mouth 15// landmarks when both images carry a mouth); needs both images landmarked by a passing eye pair 16// tilt_delta_deg10 the larger of the two roll-corrected canthal-tilt differences; needs both tilts measured 17// struct_l1 nx_visdesc_l1 between the two aligned crops' descriptors 18// struct_cos_dist NX_COSINE_Q minus the Q10 cosine of the same descriptors (0 = parallel) 19// tone_dl_e3, tone_da_e3, tone_db_e3 the absolute L*, a*, b* differences (x1000) of the skin-masked linear mean of each crop; 20// need skin pixels in both crops 21// THE SAME-PERSON BAND is not a number anyone typed: `lk_band` measures every axis on EVERY PAIR of reference stills of the one 22// person and the band per axis is the WORST (largest) distance any same-person pair reached. A subject passes an axis iff its 23// distance to its NEAREST reference is within that band; the verdict is the count of axes inside, outside and unmeasured, and 24// INSIDE only when no measured axis is outside (the MIN-headline doctrine: no axis may be compensated by another, so there is no 25// weighted sum and no coefficient). The band prints the pair that set it and how many pairs derived it. 26// EVERY NUMBER ANNOUNCES ITS RESOURCE. The canvas side is TWICE THE SMALLEST REFERENCE EYE SPAN, floored to the descriptor's grid 27// (no reference is ever upsampled; a subject smaller than that is announced as upsampled); the eye line sits at 340 permil of the 28// canvas height because on the anchor oracle row (ri_diora_baird_14: brow top 127.8 px, eye line 197.3 px, eye span 103.4 px) the 29// brow top sits 680 permil of the eye span above the eye line, so with a canvas two spans wide the brow top lands on the canvas's 30// top edge and the bottom edge falls 1.32 spans below the eye line -- past the stomion (1.15 spans on the same row) and above the 31// chin (1.65). The smallest canvas is VD_GRID cells of three pixels, the least a Sobel neighbourhood needs. 32// ALIGNMENT SOURCES ARE NAMED, NEVER SILENT. A still with an oracle row (knowledge/refintake/<slug>.oracle, matched by basename) 33// aligns on the oracle's canthi at the oracle's tenth-pixel precision; a subject without one runs the photo ruler's cascade route, 34// then its skin route, and when the skin route REFUSES its eye pair but still holds candidate canthi, those candidates align the 35// crop while the geometry and tilt axes read UNMEASURED (the pair was refused, so its landmarks are not evidence). MEASURED the day 36// this was written: on the rejected render diora_baird_close.png the cascade's face box was the nose tip and no eye pair followed, 37// and the route chooser never reaches the skin route on that refusal -- a subject the ruler cannot align reads UNMEASURED, which is 38// neither INSIDE nor OUTSIDE, so the ladder exists to turn "could not look" into a named, degraded look wherever one is possible. 39// Sides follow the image: R is the image-left eye (the subject's right), as the photo ruler and the oracle rows agree. 40// LIB, no main. license_tier: ORIGINAL No hw writes (Rule 26). 41import "nx_syscalls.nx" 42import "nx_photomark_lib.nx" 43import "nx_visdesc.nx" 44import "nx_cosine_similarity.nx" 45 46const LK_I64: i64 = 8 47const LK_BPP: i64 = 3 48const LK_BYTE: i64 = 255 49const LK_DECI: i64 = 10 // landmarks are carried in tenths of a pixel: the oracle's own unit 50const LK_DECI_HALF: i64 = 5 51const LK_PERMIL: i64 = 1000 52const LK_Q8: i64 = 256 // bilinear weights in 1/256 of a pixel 53const LK_Q8_HALF: i64 = 128 54const LK_NL: i64 = 10 55const LK_CR: i64 = 13 56const LK_SPACE: i64 = 32 57const LK_TAB: i64 = 9 58const LK_HASH: i64 = 35 59const LK_AT: i64 = 64 60const LK_SLASH: i64 = 47 61const LK_PNG_SIG0: i64 = 137 62const LK_JPEG_SIG0: i64 = 255 63const LK_DIRECTIVE_ORACLE: *u8 = "@oracle " 64// the canvas (see the header for the derivation of every one of these) 65const LK_EYE_Y_PERMIL: i64 = 340 66const LK_EYE_Y_BASIS_BROW_PERMIL: i64 = 680 67const LK_EYE_Y_BASIS: *u8 = "ri_diora_baird_14" 68const LK_CANVAS_SPANS: i64 = 2 69const LK_CANVAS_QUARTER: i64 = 4 // the eye centres sit a quarter of the width in from each side: two spans wide 70const LK_SOBEL_CELL: i64 = 3 // the least pixels a descriptor cell needs to hold one interior Sobel neighbourhood 71// the oracle row's tilt columns (the header's own order after the brow point: tilt_R_deg10, tilt_L_deg10, ICI, ICF, lip, exo, bbox) 72const LK_OF_TILT_R: i64 = 21 73const LK_OF_TILT_L: i64 = 22 74// alignment sources 75const LK_AL_NONE: i64 = 0 76const LK_AL_ORACLE: i64 = 1 77const LK_AL_CASCADE: i64 = 2 78const LK_AL_SKIN: i64 = 3 79const LK_AL_CANDIDATES: i64 = 4 80// load statuses 81const LK_OK: i64 = 0 82const LK_E_READ: i64 = 0 - 1 83const LK_E_DECODE: i64 = 0 - 2 84const LK_E_UNALIGNED: i64 = 0 - 3 85// the landmark points, in the record's order 86const LK_P_RLAT: i64 = 0 87const LK_P_RMED: i64 = 1 88const LK_P_LMED: i64 = 2 89const LK_P_LLAT: i64 = 3 90const LK_P_LS: i64 = 4 91const LK_P_STO: i64 = 5 92const LK_P_LI: i64 = 6 93const LK_P_CHR: i64 = 7 94const LK_P_CHL: i64 = 8 95const LK_P_CANTHI: i64 = 4 96const LK_P_N: i64 = 9 97// the image record 98const LK_I_RGB: i64 = 0 99const LK_I_W: i64 = 1 100const LK_I_H: i64 = 2 101const LK_I_CODEC: i64 = 3 102const LK_I_ALIGN: i64 = 4 103const LK_I_LM: i64 = 5 // LK_P_N points, x then y, deci-pixels: slots 5..22 104const LK_I_MOUTH: i64 = 23 105const LK_I_TILT_R: i64 = 24 106const LK_I_TILT_L: i64 = 25 107const LK_I_TILT_OK: i64 = 26 108const LK_I_ERX: i64 = 27 // the eye centres (midpoint of each eye's canthi), deci-pixels 109const LK_I_ERY: i64 = 28 110const LK_I_ELX: i64 = 29 111const LK_I_ELY: i64 = 30 112const LK_I_IOD: i64 = 31 // the eye-centre span, deci-pixels 113const LK_I_EYES: i64 = 32 // 1 when the canthi are landmarks (a passing eye pair or an oracle row) 114const LK_I_STATUS: i64 = 33 115const LK_I_ROUTE: i64 = 34 116const LK_I_PAIRFAIL: i64 = 35 117const LK_I_UPSAMPLED: i64 = 36 118const LK_I_N: i64 = 37 119// the feature record of one aligned crop 120const LK_F_CROP: i64 = 0 121const LK_F_GRAY: i64 = 1 122const LK_F_SKIN_N: i64 = 2 123const LK_F_L: i64 = 3 124const LK_F_A: i64 = 4 125const LK_F_B: i64 = 5 126const LK_F_GRID: i64 = 6 // cells per side of the shading and texture grids 127const LK_F_SHADE: i64 = 7 // pointer: grid*grid cell-mean luminances 128const LK_F_TEX: i64 = 8 // pointer: grid*grid cell gradient energies, normalised to sum LK_PERMIL 129const LK_F_TEXE: i64 = 9 // gradient energy per interior pixel (|gx| + |gy|, Sobel) 130const LK_F_VD: i64 = 10 // nx_visdesc_dim() slots follow 131// the axes 132const LK_AX_GEOM: i64 = 0 133const LK_AX_TILT: i64 = 1 134const LK_AX_SL1: i64 = 2 135const LK_AX_SCOS: i64 = 3 136const LK_AX_TL: i64 = 4 137const LK_AX_TA: i64 = 5 138const LK_AX_TB: i64 = 6 139const LK_AX_SHADE: i64 = 7 // NX_COSINE_Q minus the cosine of the two CENTRED shading grids (Pearson r as a Q10 distance) 140const LK_AX_TEX: i64 = 8 // L1 between the two normalised texture-energy layouts (0..2 * LK_PERMIL) 141const LK_AX_TEXE: i64 = 9 // absolute difference of gradient energy per pixel 142const LK_AX_N: i64 = 10 143const LK_AXR_MEAS: i64 = 10 // LK_AX_N flags: 1 measured, 0 unmeasured (slots 10..19) 144const LK_AXR_GEOM_PTS: i64 = 20 145const LK_AXR_COS: i64 = 21 146const LK_AXR_N: i64 = 22 147// the band record: per axis the worst same-person distance, the mean, the pair that set the worst, the pairs that measured it 148const LK_B_MAX: i64 = 0 // LK_AX_N slots each 149const LK_B_MEAN: i64 = 10 150const LK_B_PI: i64 = 20 151const LK_B_PJ: i64 = 30 152const LK_B_PAIRS: i64 = 40 153const LK_B_TOTAL: i64 = 50 154const LK_B_N: i64 = 51 155// the score record of one subject against a reference set 156const LK_S_DIST: i64 = 0 // LK_AX_N slots: the nearest reference's distance per axis 157const LK_S_NEAR: i64 = 10 // LK_AX_N slots: which reference 158const LK_S_STATE: i64 = 20 // LK_AX_N slots: LK_ST_* 159const LK_S_INSIDE: i64 = 30 160const LK_S_OUTSIDE: i64 = 31 161const LK_S_UNMEASURED: i64 = 32 162const LK_S_VERDICT: i64 = 33 163const LK_S_N: i64 = 34 164const LK_ST_UNMEASURED: i64 = 0 165const LK_ST_INSIDE: i64 = 1 166const LK_ST_OUTSIDE: i64 = 2 167// verdicts (also the CLI's exit codes) 168const LK_V_INSIDE: i64 = 0 169const LK_V_OUTSIDE: i64 = 1 170const LK_V_UNMEASURED: i64 = 5 171const LK_V_NOBAND: i64 = 6 172// the context: the loaded cascades, the oracle rows, the reference list's oracle path 173const LK_C_FACE: i64 = 0 174const LK_C_EYE: i64 = 1 175const LK_C_OBUF: i64 = 2 176const LK_C_OLEN: i64 = 3 177const LK_C_OPATH: i64 = 4 178const LK_C_FACE_RC: i64 = 5 179const LK_C_EYE_RC: i64 = 6 180const LK_C_N: i64 = 7 181 182func lk_ruler_version() -> i64 { return 1 } 183func lkh_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 184func lkh_max(a: i64, b: i64) -> i64 { if a > b { return a } return b } 185func lkh_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } 186// division rounded half away from zero; den > 0 187func lkh_div_round(num: i64, den: i64) -> i64 { 188 if num >= 0 { return (num + den / 2) / den } 189 return 0 - ((0 - num + den / 2) / den) 190} 191// floor division; den > 0 192func lkh_div_floor(num: i64, den: i64) -> i64 { 193 var q: i64 = num / den 194 if num < 0 { if q * den != num { q = q - 1 } } 195 return q 196} 197func lkh_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 198func lkh_streq(a: *u8, b: *u8) -> i64 { 199 var i: i64 = 0 200 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 201 if b[i] != (0 as u8) { return 0 } 202 return 1 203} 204// the basename of a path: the bytes after the last slash 205func lkh_basename(p: *u8) -> *u8 { 206 var i: i64 = 0 207 var last: i64 = 0 208 while p[i] != (0 as u8) { if (p[i] as i64) == LK_SLASH { last = i + 1 } i = i + 1 } 209 return (p as i64 + last) as *u8 210} 211// a NUL-terminated copy of buf[s..e) 212func lkh_span_copy(buf: *u8, s: i64, e: i64) -> *u8 { 213 let out: *u8 = sys_mmap(e - s + 1) 214 var i: i64 = s 215 var o: i64 = 0 216 while i < e { out[o] = buf[i]; o = o + 1; i = i + 1 } 217 out[o] = 0 as u8 218 return out 219} 220func lk_align_name(k: i64) -> *u8 { 221 if k == LK_AL_ORACLE { return "oracle" as *u8 } 222 if k == LK_AL_CASCADE { return "ruler-cascade" as *u8 } 223 if k == LK_AL_SKIN { return "ruler-skin" as *u8 } 224 if k == LK_AL_CANDIDATES { return "ruler-candidates-pair-refused" as *u8 } 225 return "none" as *u8 226} 227func lk_axis_name(k: i64) -> *u8 { 228 if k == LK_AX_GEOM { return "geom_nme_permil" as *u8 } 229 if k == LK_AX_TILT { return "tilt_delta_deg10" as *u8 } 230 if k == LK_AX_SL1 { return "struct_l1" as *u8 } 231 if k == LK_AX_SCOS { return "struct_cos_dist_q10" as *u8 } 232 if k == LK_AX_TL { return "tone_dl_e3" as *u8 } 233 if k == LK_AX_TA { return "tone_da_e3" as *u8 } 234 if k == LK_AX_TB { return "tone_db_e3" as *u8 } 235 if k == LK_AX_SHADE { return "shade_cos_dist_q10" as *u8 } 236 if k == LK_AX_TEX { return "texture_layout_l1_permil" as *u8 } 237 if k == LK_AX_TEXE { return "texture_energy_per_px_delta" as *u8 } 238 return "axis" as *u8 239} 240func lk_state_name(k: i64) -> *u8 { 241 if k == LK_ST_INSIDE { return "INSIDE" as *u8 } 242 if k == LK_ST_OUTSIDE { return "OUTSIDE" as *u8 } 243 return "UNMEASURED" as *u8 244} 245func lk_verdict_name(k: i64) -> *u8 { 246 if k == LK_V_INSIDE { return "INSIDE" as *u8 } 247 if k == LK_V_OUTSIDE { return "OUTSIDE" as *u8 } 248 if k == LK_V_NOBAND { return "NO-BAND" as *u8 } 249 return "UNMEASURED" as *u8 250} 251 252// ---- the context ------------------------------------------------------------------------------------------------------------ 253func lk_ctx_new() -> *i64 { 254 let ctx: *i64 = sys_mmap(LK_C_N * LK_I64) as *i64 255 let rc: *i64 = sys_mmap(2 * LK_I64) as *i64 256 ctx[LK_C_FACE] = ff_load_face(rc) as i64 257 ctx[LK_C_FACE_RC] = rc[0] 258 rc[0] = 0 259 ctx[LK_C_EYE] = ff_load_eye(rc) as i64 260 ctx[LK_C_EYE_RC] = rc[0] 261 return ctx 262} 263func lk_ctx_cascades(ctx: *i64) -> i64 { 264 if ctx[LK_C_FACE] == 0 { return 0 } 265 if ctx[LK_C_EYE] == 0 { return 0 } 266 return 1 267} 268// load the oracle rows; returns the byte count read, 0 when absent (the ruler then aligns everything itself) 269func lk_ctx_oracle(ctx: *i64, path: *u8) -> i64 { 270 ctx[LK_C_OPATH] = path as i64 271 let fl: *i64 = sys_mmap(2 * LK_I64) as *i64 272 fl[0] = 0 273 let buf: *u8 = sys_read_file(path, fl) 274 if (buf as i64) == 0 { ctx[LK_C_OBUF] = 0; ctx[LK_C_OLEN] = 0; return 0 } 275 ctx[LK_C_OBUF] = buf as i64 276 ctx[LK_C_OLEN] = fl[0] 277 return fl[0] 278} 279 280// ---- the image record -------------------------------------------------------------------------------------------------------- 281func lk_img_new() -> *i64 { 282 let img: *i64 = sys_mmap(LK_I_N * LK_I64) as *i64 283 var k: i64 = 0 284 while k < LK_I_N { img[k] = 0; k = k + 1 } 285 return img 286} 287func lk_img_set_rgb(img: *i64, rgb: *u8, w: i64, h: i64) -> i64 { 288 img[LK_I_RGB] = rgb as i64 289 img[LK_I_W] = w 290 img[LK_I_H] = h 291 return 0 292} 293func lk_pt_x(img: *i64, p: i64) -> i64 { return img[LK_I_LM + 2 * p] } 294func lk_pt_y(img: *i64, p: i64) -> i64 { return img[LK_I_LM + 2 * p + 1] } 295func lk_pt_set(img: *i64, p: i64, x: i64, y: i64) -> i64 { img[LK_I_LM + 2 * p] = x; img[LK_I_LM + 2 * p + 1] = y; return 0 } 296// derive the eye centres and the span from the four canthi; returns 1 when the span is positive 297func lk_img_finish(img: *i64) -> i64 { 298 img[LK_I_ERX] = lkh_div_round(lk_pt_x(img, LK_P_RLAT) + lk_pt_x(img, LK_P_RMED), 2) 299 img[LK_I_ERY] = lkh_div_round(lk_pt_y(img, LK_P_RLAT) + lk_pt_y(img, LK_P_RMED), 2) 300 img[LK_I_ELX] = lkh_div_round(lk_pt_x(img, LK_P_LMED) + lk_pt_x(img, LK_P_LLAT), 2) 301 img[LK_I_ELY] = lkh_div_round(lk_pt_y(img, LK_P_LMED) + lk_pt_y(img, LK_P_LLAT), 2) 302 let dx: i64 = img[LK_I_ELX] - img[LK_I_ERX] 303 let dy: i64 = img[LK_I_ELY] - img[LK_I_ERY] 304 img[LK_I_IOD] = pm_isqrt(dx * dx + dy * dy) 305 if img[LK_I_IOD] <= 0 { img[LK_I_STATUS] = LK_E_UNALIGNED; img[LK_I_ALIGN] = LK_AL_NONE; return 0 } 306 img[LK_I_STATUS] = LK_OK 307 return 1 308} 309// an "x10,y10" oracle field kept at the oracle's own tenth-pixel unit (pm_field_xy divides down to pixels, which throws the tenth 310// away on a 45-pixel eye span); out2 = x10, y10 311func lk_field_xy10(buf: *u8, ls: i64, le: i64, idx: i64, out2: *i64) -> i64 { 312 let fb: *i64 = sys_mmap(2 * LK_I64) as *i64 313 if pm_field(buf, ls, le, idx, fb) == 0 { return 0 } 314 let t: *i64 = sys_mmap(2 * LK_I64) as *i64 315 if pm_span_int(buf, fb[0], fb[1], t) == 0 { return 0 } 316 let x10: i64 = t[0] 317 if t[1] >= fb[1] { return 0 } 318 if (buf[t[1]] as i64) != PM_COMMA { return 0 } 319 if pm_span_int(buf, t[1] + 1, fb[1], t) == 0 { return 0 } 320 out2[0] = x10 321 out2[1] = t[0] 322 return 1 323} 324// the end of the line that starts at ls (the offset of its newline, or n) 325func lkh_line_end(buf: *u8, ls: i64, n: i64) -> i64 { 326 var le: i64 = ls 327 var go: i64 = 1 328 while go == 1 { 329 if le >= n { go = 0 } else { if (buf[le] as i64) == LK_NL { go = 0 } else { le = le + 1 } } 330 } 331 return le 332} 333// find the oracle row whose file field is base; out2 = row start, row end; returns 1 when found 334func lk_oracle_row(ctx: *i64, base: *u8, out2: *i64) -> i64 { 335 let buf: *u8 = ctx[LK_C_OBUF] as *u8 336 let n: i64 = ctx[LK_C_OLEN] 337 if n <= 0 { return 0 } 338 let fb: *i64 = sys_mmap(2 * LK_I64) as *i64 339 let bl: i64 = lkh_slen(base) 340 var ls: i64 = 0 341 while ls < n { 342 let le: i64 = lkh_line_end(buf, ls, n) 343 if le > ls { if (buf[ls] as i64) != LK_HASH { if pm_field(buf, ls, le, PM_OF_FILE, fb) == 1 { 344 if fb[1] - fb[0] == bl { 345 var same: i64 = 1 346 var i: i64 = 0 347 while i < bl { if buf[fb[0] + i] != base[i] { same = 0 } i = i + 1 } 348 if same == 1 { out2[0] = ls; out2[1] = le; return 1 } 349 } 350 } } } 351 ls = le + 1 352 } 353 return 0 354} 355// landmarks from an oracle row: the four canthi, the mouth as the five points this ruler carries (labrale superius = ulip_top, 356// stomion = the midpoint of the two inner vermilion edges, labrale inferius = llip_bot, the two cheilia), the two tilts 357func lk_landmarks_from_oracle(buf: *u8, ls: i64, le: i64, img: *i64) -> i64 { 358 if pm_field_int(buf, ls, le, PM_OF_FACES) != 1 { return 0 } 359 let xy: *i64 = sys_mmap(2 * LK_I64) as *i64 360 var ok: i64 = 1 361 if lk_field_xy10(buf, ls, le, PM_OF_RLAT, xy) == 0 { ok = 0 } else { lk_pt_set(img, LK_P_RLAT, xy[0], xy[1]) } 362 if lk_field_xy10(buf, ls, le, PM_OF_RMED, xy) == 0 { ok = 0 } else { lk_pt_set(img, LK_P_RMED, xy[0], xy[1]) } 363 if lk_field_xy10(buf, ls, le, PM_OF_LMED, xy) == 0 { ok = 0 } else { lk_pt_set(img, LK_P_LMED, xy[0], xy[1]) } 364 if lk_field_xy10(buf, ls, le, PM_OF_LLAT, xy) == 0 { ok = 0 } else { lk_pt_set(img, LK_P_LLAT, xy[0], xy[1]) } 365 if ok == 0 { return 0 } 366 img[LK_I_EYES] = 1 367 var mok: i64 = 1 368 if lk_field_xy10(buf, ls, le, PM_OF_ULIP_TOP, xy) == 0 { mok = 0 } else { lk_pt_set(img, LK_P_LS, xy[0], xy[1]) } 369 var ix: i64 = 0 370 var iy: i64 = 0 371 if lk_field_xy10(buf, ls, le, PM_OF_ULIP_IN, xy) == 0 { mok = 0 } else { ix = xy[0]; iy = xy[1] } 372 if lk_field_xy10(buf, ls, le, PM_OF_LLIP_IN, xy) == 0 { mok = 0 } else { lk_pt_set(img, LK_P_STO, lkh_div_round(ix + xy[0], 2), lkh_div_round(iy + xy[1], 2)) } 373 if lk_field_xy10(buf, ls, le, PM_OF_LLIP_BOT, xy) == 0 { mok = 0 } else { lk_pt_set(img, LK_P_LI, xy[0], xy[1]) } 374 if lk_field_xy10(buf, ls, le, PM_OF_MOUTH_R, xy) == 0 { mok = 0 } else { lk_pt_set(img, LK_P_CHR, xy[0], xy[1]) } 375 if lk_field_xy10(buf, ls, le, PM_OF_MOUTH_L, xy) == 0 { mok = 0 } else { lk_pt_set(img, LK_P_CHL, xy[0], xy[1]) } 376 img[LK_I_MOUTH] = mok 377 img[LK_I_TILT_R] = pm_field_int(buf, ls, le, LK_OF_TILT_R) 378 img[LK_I_TILT_L] = pm_field_int(buf, ls, le, LK_OF_TILT_L) 379 img[LK_I_TILT_OK] = 1 380 img[LK_I_ALIGN] = LK_AL_ORACLE 381 return 1 382} 383// landmarks from the photo ruler's result record (pixels -> deci-pixels) 384func lk_landmarks_from_ruler(res: *i64, img: *i64, align: i64) -> i64 { 385 lk_pt_set(img, LK_P_RLAT, res[PM_R_RLAT_X] * LK_DECI, res[PM_R_RLAT_Y] * LK_DECI) 386 lk_pt_set(img, LK_P_RMED, res[PM_R_RMED_X] * LK_DECI, res[PM_R_RMED_Y] * LK_DECI) 387 lk_pt_set(img, LK_P_LMED, res[PM_R_LMED_X] * LK_DECI, res[PM_R_LMED_Y] * LK_DECI) 388 lk_pt_set(img, LK_P_LLAT, res[PM_R_LLAT_X] * LK_DECI, res[PM_R_LLAT_Y] * LK_DECI) 389 var eyes: i64 = 0 390 if res[PM_R_EYES] == 2 { eyes = 1 } 391 img[LK_I_EYES] = eyes 392 img[LK_I_TILT_OK] = eyes 393 img[LK_I_TILT_R] = res[PM_R_TILT_R] 394 img[LK_I_TILT_L] = res[PM_R_TILT_L] 395 img[LK_I_MOUTH] = 0 396 if eyes == 1 { if res[PM_R_MOUTH] == 1 { 397 lk_pt_set(img, LK_P_LS, res[PM_R_LS_X] * LK_DECI, res[PM_R_LS_Y] * LK_DECI) 398 lk_pt_set(img, LK_P_STO, res[PM_R_STO_X] * LK_DECI, res[PM_R_STO_Y] * LK_DECI) 399 lk_pt_set(img, LK_P_LI, res[PM_R_LI_X] * LK_DECI, res[PM_R_LI_Y] * LK_DECI) 400 lk_pt_set(img, LK_P_CHR, res[PM_R_CHR_X] * LK_DECI, res[PM_R_CHR_Y] * LK_DECI) 401 lk_pt_set(img, LK_P_CHL, res[PM_R_CHL_X] * LK_DECI, res[PM_R_CHL_Y] * LK_DECI) 402 img[LK_I_MOUTH] = 1 403 } } 404 img[LK_I_ALIGN] = align 405 img[LK_I_ROUTE] = res[PM_R_ROUTE] 406 img[LK_I_PAIRFAIL] = res[PM_R_PAIR_FAIL] 407 return 1 408} 409// THE ALIGNMENT LADDER for a decoded image without an oracle row: the cascade route's eye pair, the skin route's eye pair, the 410// skin route's refused-pair candidates (alignment only). Returns 1 when something aligned it. 411func lk_align_ruler(ctx: *i64, img: *i64) -> i64 { 412 let rgb: *u8 = img[LK_I_RGB] as *u8 413 let w: i64 = img[LK_I_W] 414 let h: i64 = img[LK_I_H] 415 let res: *i64 = sys_mmap(PM_R_N * LK_I64) as *i64 416 var k: i64 = 0 417 if lk_ctx_cascades(ctx) == 1 { 418 pm_landmarks_cascade(rgb, w, h, res, ctx[LK_C_FACE] as *HaarCascade, ctx[LK_C_EYE] as *HaarCascade) 419 if res[PM_R_EYES] == 2 { lk_landmarks_from_ruler(res, img, LK_AL_CASCADE); return lk_img_finish(img) } 420 } 421 k = 0 422 while k < PM_R_N { res[k] = 0; k = k + 1 } 423 pm_landmarks(rgb, w, h, res) 424 if res[PM_R_EYES] == 2 { lk_landmarks_from_ruler(res, img, LK_AL_SKIN); return lk_img_finish(img) } 425 if res[PM_R_PAIR_FAIL] != PM_PF_NONE { if res[PM_R_PAIR_FAIL] != PM_PF_EMPTY { 426 var any: i64 = 0 427 if res[PM_R_RLAT_X] != 0 { any = 1 } 428 if res[PM_R_LLAT_X] != 0 { any = 1 } 429 if any == 1 { 430 lk_landmarks_from_ruler(res, img, LK_AL_CANDIDATES) 431 img[LK_I_EYES] = 0 432 img[LK_I_TILT_OK] = 0 433 img[LK_I_MOUTH] = 0 434 return lk_img_finish(img) 435 } 436 } } 437 img[LK_I_STATUS] = LK_E_UNALIGNED 438 img[LK_I_ALIGN] = LK_AL_NONE 439 return 0 440} 441// DECODE a still or a render and align it: the oracle row when one names its basename, the ruler's ladder otherwise 442func lk_load(ctx: *i64, path: *u8, img: *i64) -> i64 { 443 let pix: *i64 = sys_mmap(LK_I64) as *i64 444 let d3: *i64 = sys_mmap(3 * LK_I64) as *i64 445 let dc: i64 = pm_decode_file(path, pix, d3) 446 if dc == PM_E_READ { img[LK_I_STATUS] = LK_E_READ; return LK_E_READ } 447 if dc != PM_OK { img[LK_I_STATUS] = LK_E_DECODE; return LK_E_DECODE } 448 lk_img_set_rgb(img, pix[0] as *u8, d3[0], d3[1]) 449 img[LK_I_CODEC] = d3[2] 450 let row: *i64 = sys_mmap(2 * LK_I64) as *i64 451 if lk_oracle_row(ctx, lkh_basename(path), row) == 1 { 452 if lk_landmarks_from_oracle(ctx[LK_C_OBUF] as *u8, row[0], row[1], img) == 1 { 453 if lk_img_finish(img) == 1 { return LK_OK } 454 } 455 } 456 if lk_align_ruler(ctx, img) == 1 { return LK_OK } 457 return LK_E_UNALIGNED 458} 459 460// ---- the similarity between two eye frames -------------------------------------------------------------------------------- 461// map point p from frame A (eyes at aR, aL) to frame B (eyes at bR, bL): the one similarity that carries aR to bR and aL to bL, 462// as the complex ratio (bL - bR) / (aL - aR) applied to p - aR. All deci-pixels. 463func lk_map(aRx: i64, aRy: i64, aLx: i64, aLy: i64, bRx: i64, bRy: i64, bLx: i64, bLy: i64, px: i64, py: i64, out2: *i64) -> i64 { 464 let ax: i64 = aLx - aRx 465 let ay: i64 = aLy - aRy 466 let bx: i64 = bLx - bRx 467 let by: i64 = bLy - bRy 468 let n: i64 = ax * ax + ay * ay 469 if n == 0 { out2[0] = px; out2[1] = py; return 0 } 470 let rx: i64 = bx * ax + by * ay 471 let ry: i64 = by * ax - bx * ay 472 let vx: i64 = px - aRx 473 let vy: i64 = py - aRy 474 out2[0] = bRx + lkh_div_round(rx * vx - ry * vy, n) 475 out2[1] = bRy + lkh_div_round(rx * vy + ry * vx, n) 476 return 1 477} 478// bilinear sample of an interleaved RGB image at a deci-pixel position (pixel i covers [10i, 10i+10), its centre at 10i+5); 479// positions off the image clamp to its edge 480func lk_sample(rgb: *u8, w: i64, h: i64, sx: i64, sy: i64, out3: *i64) -> i64 { 481 let fx256: i64 = lkh_div_floor(sx * LK_Q8, LK_DECI) - LK_Q8_HALF 482 let fy256: i64 = lkh_div_floor(sy * LK_Q8, LK_DECI) - LK_Q8_HALF 483 let x0f: i64 = lkh_div_floor(fx256, LK_Q8) 484 let y0f: i64 = lkh_div_floor(fy256, LK_Q8) 485 let wx: i64 = fx256 - x0f * LK_Q8 486 let wy: i64 = fy256 - y0f * LK_Q8 487 var x0: i64 = x0f 488 var x1: i64 = x0f + 1 489 var y0: i64 = y0f 490 var y1: i64 = y0f + 1 491 if x0 < 0 { x0 = 0 } 492 if x1 < 0 { x1 = 0 } 493 if y0 < 0 { y0 = 0 } 494 if y1 < 0 { y1 = 0 } 495 if x0 > w - 1 { x0 = w - 1 } 496 if x1 > w - 1 { x1 = w - 1 } 497 if y0 > h - 1 { y0 = h - 1 } 498 if y1 > h - 1 { y1 = h - 1 } 499 let o00: i64 = (y0 * w + x0) * LK_BPP 500 let o10: i64 = (y0 * w + x1) * LK_BPP 501 let o01: i64 = (y1 * w + x0) * LK_BPP 502 let o11: i64 = (y1 * w + x1) * LK_BPP 503 var c: i64 = 0 504 while c < LK_BPP { 505 let v00: i64 = (rgb[o00 + c] as i64) & LK_BYTE 506 let v10: i64 = (rgb[o10 + c] as i64) & LK_BYTE 507 let v01: i64 = (rgb[o01 + c] as i64) & LK_BYTE 508 let v11: i64 = (rgb[o11 + c] as i64) & LK_BYTE 509 let top: i64 = v00 * (LK_Q8 - wx) + v10 * wx 510 let bot: i64 = v01 * (LK_Q8 - wx) + v11 * wx 511 out3[c] = lkh_div_round(top * (LK_Q8 - wy) + bot * wy, LK_Q8 * LK_Q8) 512 c = c + 1 513 } 514 return 0 515} 516// resample an aligned image into an ow x oh frame whose eye centres are dR and dL (deci-pixels): every destination pixel centre 517// is carried back into the source by the similarity and sampled bilinearly 518func lk_resample(img: *i64, dRx: i64, dRy: i64, dLx: i64, dLy: i64, ow: i64, oh: i64) -> *u8 { 519 let out: *u8 = sys_mmap(ow * oh * LK_BPP + 16) 520 let p2: *i64 = sys_mmap(2 * LK_I64) as *i64 521 let s3: *i64 = sys_mmap(3 * LK_I64) as *i64 522 var y: i64 = 0 523 while y < oh { 524 var x: i64 = 0 525 while x < ow { 526 lk_map(dRx, dRy, dLx, dLy, img[LK_I_ERX], img[LK_I_ERY], img[LK_I_ELX], img[LK_I_ELY], x * LK_DECI + LK_DECI_HALF, y * LK_DECI + LK_DECI_HALF, p2) 527 lk_sample(img[LK_I_RGB] as *u8, img[LK_I_W], img[LK_I_H], p2[0], p2[1], s3) 528 let o: i64 = (y * ow + x) * LK_BPP 529 out[o] = s3[0] as u8 530 out[o + 1] = s3[1] as u8 531 out[o + 2] = s3[2] as u8 532 x = x + 1 533 } 534 y = y + 1 535 } 536 return out 537} 538// carry one image's landmarks into another frame (the planted-roll control builds its own record this way) 539func lk_img_map_landmarks(src: *i64, dst: *i64, dRx: i64, dRy: i64, dLx: i64, dLy: i64) -> i64 { 540 let p2: *i64 = sys_mmap(2 * LK_I64) as *i64 541 var p: i64 = 0 542 while p < LK_P_N { 543 lk_map(src[LK_I_ERX], src[LK_I_ERY], src[LK_I_ELX], src[LK_I_ELY], dRx, dRy, dLx, dLy, lk_pt_x(src, p), lk_pt_y(src, p), p2) 544 lk_pt_set(dst, p, p2[0], p2[1]) 545 p = p + 1 546 } 547 dst[LK_I_MOUTH] = src[LK_I_MOUTH] 548 dst[LK_I_EYES] = src[LK_I_EYES] 549 dst[LK_I_TILT_OK] = src[LK_I_TILT_OK] 550 dst[LK_I_TILT_R] = src[LK_I_TILT_R] 551 dst[LK_I_TILT_L] = src[LK_I_TILT_L] 552 dst[LK_I_ALIGN] = src[LK_I_ALIGN] 553 return lk_img_finish(dst) 554} 555 556// ---- the canvas ------------------------------------------------------------------------------------------------------------- 557// the canvas side from the smallest reference eye span (deci-pixels): two spans, floored to the descriptor grid, never below the 558// grid's Sobel floor 559func lk_canvas(min_iod_deci: i64) -> i64 { 560 var c: i64 = LK_CANVAS_SPANS * min_iod_deci / LK_DECI 561 c = c - c % VD_GRID 562 let floor_c: i64 = VD_GRID * LK_SOBEL_CELL 563 if c < floor_c { c = floor_c } 564 return c 565} 566func lk_canvas_eye_rx(c: i64) -> i64 { return (c / LK_CANVAS_QUARTER) * LK_DECI } 567func lk_canvas_eye_lx(c: i64) -> i64 { return (c - c / LK_CANVAS_QUARTER) * LK_DECI } 568func lk_canvas_eye_y(c: i64) -> i64 { return (c * LK_EYE_Y_PERMIL / LK_PERMIL) * LK_DECI } 569func lk_canvas_span(c: i64) -> i64 { return lk_canvas_eye_lx(c) - lk_canvas_eye_rx(c) } 570// the aligned crop of an image on a canvas of side c 571func lk_crop(img: *i64, c: i64) -> *u8 { 572 return lk_resample(img, lk_canvas_eye_rx(c), lk_canvas_eye_y(c), lk_canvas_eye_lx(c), lk_canvas_eye_y(c), c, c) 573} 574func lk_feat_new() -> *i64 { 575 let n: i64 = LK_F_VD + nx_visdesc_dim() 576 let f: *i64 = sys_mmap(n * LK_I64) as *i64 577 var k: i64 = 0 578 while k < n { f[k] = 0; k = k + 1 } 579 return f 580} 581func lk_feat_vd(feat: *i64) -> *i64 { return (feat as i64 + LK_F_VD * LK_I64) as *i64 } 582// THE SHADING AND TEXTURE GRIDS' CELL SIZE IS THE REFERENCE SET'S OWN LANDMARK SPREAD: the geometry band (the worst same-person 583// landmark NME, permil of the canvas eye span) is the spatial precision the set supports, so a cell is that many canvas pixels and 584// nothing finer is compared -- never below the Sobel floor. Returns cells per side. 585func lk_grid_from_band(c: i64, geom_band_permil: i64) -> i64 { 586 var cell: i64 = lkh_div_round(geom_band_permil * (lk_canvas_span(c) / LK_DECI), LK_PERMIL) 587 if cell < LK_SOBEL_CELL { cell = LK_SOBEL_CELL } 588 var g: i64 = c / cell 589 if g < 1 { g = 1 } 590 return g 591} 592// the features of one aligned crop: the edge-orientation descriptor, the shading grid, the texture-energy grid, the skin-masked Lab mean 593func lk_features(img: *i64, c: i64, grid: i64, feat: *i64) -> i64 { 594 let crop: *u8 = lk_crop(img, c) 595 feat[LK_F_CROP] = crop as i64 596 let gray: *u8 = sys_mmap(c * c + 16) 597 var i: i64 = 0 598 while i < c * c { 599 let o: i64 = i * LK_BPP 600 gray[i] = pm_luma((crop[o] as i64) & LK_BYTE, (crop[o + 1] as i64) & LK_BYTE, (crop[o + 2] as i64) & LK_BYTE) as u8 601 i = i + 1 602 } 603 feat[LK_F_GRAY] = gray as i64 604 nx_visdesc_extract(gray, c, c, lk_feat_vd(feat)) 605 // the shading grid (cell-mean luminance) and the texture grid (cell gradient energy over interior pixels, Sobel from nx_image) 606 feat[LK_F_GRID] = grid 607 let ncell: i64 = grid * grid 608 let shade: *i64 = sys_mmap(ncell * LK_I64) as *i64 609 let tex: *i64 = sys_mmap(ncell * LK_I64) as *i64 610 let im: *Image = vd_wrap_gray(gray, c, c) 611 let gx: *ImageS64 = nx_image_sobel_x(im) 612 let gy: *ImageS64 = nx_image_sobel_y(im) 613 var total: i64 = 0 614 var interior: i64 = 0 615 var cy: i64 = 0 616 while cy < grid { 617 let y0: i64 = cy * c / grid 618 let y1: i64 = (cy + 1) * c / grid 619 var cx: i64 = 0 620 while cx < grid { 621 let x0: i64 = cx * c / grid 622 let x1: i64 = (cx + 1) * c / grid 623 var lsum: i64 = 0 624 var lcnt: i64 = 0 625 var esum: i64 = 0 626 var y: i64 = y0 627 while y < y1 { 628 var x: i64 = x0 629 while x < x1 { 630 lsum = lsum + ((gray[y * c + x] as i64) & LK_BYTE) 631 lcnt = lcnt + 1 632 if x > 0 { if x < c - 1 { if y > 0 { if y < c - 1 { 633 esum = esum + lkh_abs(nx_image_s64_get(gx, x, y)) + lkh_abs(nx_image_s64_get(gy, x, y)) 634 interior = interior + 1 635 } } } } 636 x = x + 1 637 } 638 y = y + 1 639 } 640 let k: i64 = cy * grid + cx 641 shade[k] = 0 642 if lcnt > 0 { shade[k] = lsum / lcnt } 643 tex[k] = esum 644 total = total + esum 645 cx = cx + 1 646 } 647 cy = cy + 1 648 } 649 var k2: i64 = 0 650 while k2 < ncell { if total > 0 { tex[k2] = lkh_div_round(tex[k2] * LK_PERMIL, total) } else { tex[k2] = 0 } k2 = k2 + 1 } 651 feat[LK_F_SHADE] = shade as i64 652 feat[LK_F_TEX] = tex as i64 653 feat[LK_F_TEXE] = 0 654 if interior > 0 { feat[LK_F_TEXE] = total / interior } 655 let sk: *i64 = sys_mmap(SKS_R_N * LK_I64) as *i64 656 feat[LK_F_SKIN_N] = sks_measure_rgb(crop, c, c, sk) 657 feat[LK_F_L] = sk[SKS_R_L] 658 feat[LK_F_A] = sk[SKS_R_A] 659 feat[LK_F_B] = sk[SKS_R_B] 660 img[LK_I_UPSAMPLED] = 0 661 if img[LK_I_IOD] < lk_canvas_span(c) { img[LK_I_UPSAMPLED] = 1 } 662 return 0 663} 664// the cosine (Q10) of two grids after each is centred on its own mean: Pearson's r through the estate's cosine ruler 665func lk_centred_cos(a: *i64, b: *i64, n: i64) -> i64 { 666 let ca: *i64 = sys_mmap(n * LK_I64) as *i64 667 let cb: *i64 = sys_mmap(n * LK_I64) as *i64 668 var sa: i64 = 0 669 var sb: i64 = 0 670 var i: i64 = 0 671 while i < n { sa = sa + a[i]; sb = sb + b[i]; i = i + 1 } 672 let ma: i64 = lkh_div_round(sa, n) 673 let mb: i64 = lkh_div_round(sb, n) 674 i = 0 675 while i < n { ca[i] = a[i] - ma; cb[i] = b[i] - mb; i = i + 1 } 676 return nx_cosine_similarity(ca, n, cb, n) 677} 678// the geometry of one pair: mean landmark distance in the canvas, permil of the canvas eye span; out2 = permil, points used; 679// returns 1 when both images carry landmarks 680func lk_geom_pair(imgA: *i64, imgB: *i64, c: i64, out2: *i64) -> i64 { 681 out2[0] = 0 682 out2[1] = 0 683 if imgA[LK_I_EYES] != 1 { return 0 } 684 if imgB[LK_I_EYES] != 1 { return 0 } 685 let cRx: i64 = lk_canvas_eye_rx(c) 686 let cLx: i64 = lk_canvas_eye_lx(c) 687 let cY: i64 = lk_canvas_eye_y(c) 688 var pts: i64 = LK_P_CANTHI 689 if imgA[LK_I_MOUTH] == 1 { if imgB[LK_I_MOUTH] == 1 { pts = LK_P_N } } 690 let pa: *i64 = sys_mmap(2 * LK_I64) as *i64 691 let pb: *i64 = sys_mmap(2 * LK_I64) as *i64 692 var sum: i64 = 0 693 var p: i64 = 0 694 while p < pts { 695 lk_map(imgA[LK_I_ERX], imgA[LK_I_ERY], imgA[LK_I_ELX], imgA[LK_I_ELY], cRx, cY, cLx, cY, lk_pt_x(imgA, p), lk_pt_y(imgA, p), pa) 696 lk_map(imgB[LK_I_ERX], imgB[LK_I_ERY], imgB[LK_I_ELX], imgB[LK_I_ELY], cRx, cY, cLx, cY, lk_pt_x(imgB, p), lk_pt_y(imgB, p), pb) 697 let dx: i64 = pa[0] - pb[0] 698 let dy: i64 = pa[1] - pb[1] 699 sum = sum + pm_isqrt(dx * dx + dy * dy) 700 p = p + 1 701 } 702 out2[0] = lkh_div_round(sum * LK_PERMIL, pts * lk_canvas_span(c)) 703 out2[1] = pts 704 return 1 705} 706// the geometry band alone (landmarks only, no crop): the worst same-person landmark NME over every pair; pairs_out the pairs measured 707func lk_band_geometry(imgs: *i64, n: i64, c: i64, pairs_out: *i64) -> i64 { 708 let g2: *i64 = sys_mmap(2 * LK_I64) as *i64 709 var worst: i64 = 0 710 var pairs: i64 = 0 711 var i: i64 = 0 712 while i < n { 713 var j: i64 = i + 1 714 while j < n { 715 if lk_geom_pair(imgs[i] as *i64, imgs[j] as *i64, c, g2) == 1 { 716 if g2[0] > worst { worst = g2[0] } 717 pairs = pairs + 1 718 } 719 j = j + 1 720 } 721 i = i + 1 722 } 723 pairs_out[0] = pairs 724 return worst 725} 726 727// ---- the axes --------------------------------------------------------------------------------------------------------------- 728func lk_axes(imgA: *i64, featA: *i64, imgB: *i64, featB: *i64, c: i64, ax: *i64) -> i64 { 729 var k: i64 = 0 730 while k < LK_AXR_N { ax[k] = 0; k = k + 1 } 731 // GEOMETRY: both landmark sets carried into the canvas, mean point distance as permil of the canvas eye span 732 let g2: *i64 = sys_mmap(2 * LK_I64) as *i64 733 if lk_geom_pair(imgA, imgB, c, g2) == 1 { 734 ax[LK_AX_GEOM] = g2[0] 735 ax[LK_AXR_MEAS + LK_AX_GEOM] = 1 736 ax[LK_AXR_GEOM_PTS] = g2[1] 737 } 738 // TILT 739 if imgA[LK_I_TILT_OK] == 1 { if imgB[LK_I_TILT_OK] == 1 { 740 ax[LK_AX_TILT] = lkh_max(lkh_abs(imgA[LK_I_TILT_R] - imgB[LK_I_TILT_R]), lkh_abs(imgA[LK_I_TILT_L] - imgB[LK_I_TILT_L])) 741 ax[LK_AXR_MEAS + LK_AX_TILT] = 1 742 } } 743 // STRUCTURE 744 let va: *i64 = lk_feat_vd(featA) 745 let vb: *i64 = lk_feat_vd(featB) 746 ax[LK_AX_SL1] = nx_visdesc_l1(va, vb) 747 ax[LK_AXR_MEAS + LK_AX_SL1] = 1 748 let cs: i64 = nx_cosine_similarity(va, nx_visdesc_dim(), vb, nx_visdesc_dim()) 749 ax[LK_AXR_COS] = cs 750 ax[LK_AX_SCOS] = NX_COSINE_Q - cs 751 ax[LK_AXR_MEAS + LK_AX_SCOS] = 1 752 // SHADING LAYOUT and TEXTURE: the grids compare only when both crops were gridded alike 753 if featA[LK_F_GRID] > 0 { if featA[LK_F_GRID] == featB[LK_F_GRID] { 754 let ncell: i64 = featA[LK_F_GRID] * featA[LK_F_GRID] 755 ax[LK_AX_SHADE] = NX_COSINE_Q - lk_centred_cos(featA[LK_F_SHADE] as *i64, featB[LK_F_SHADE] as *i64, ncell) 756 ax[LK_AXR_MEAS + LK_AX_SHADE] = 1 757 let ta: *i64 = featA[LK_F_TEX] as *i64 758 let tb: *i64 = featB[LK_F_TEX] as *i64 759 var tl1: i64 = 0 760 var q: i64 = 0 761 while q < ncell { tl1 = tl1 + lkh_abs(ta[q] - tb[q]); q = q + 1 } 762 ax[LK_AX_TEX] = tl1 763 ax[LK_AXR_MEAS + LK_AX_TEX] = 1 764 ax[LK_AX_TEXE] = lkh_abs(featA[LK_F_TEXE] - featB[LK_F_TEXE]) 765 ax[LK_AXR_MEAS + LK_AX_TEXE] = 1 766 } } 767 // TONE 768 if featA[LK_F_SKIN_N] > 0 { if featB[LK_F_SKIN_N] > 0 { 769 ax[LK_AX_TL] = lkh_abs(featA[LK_F_L] - featB[LK_F_L]) 770 ax[LK_AX_TA] = lkh_abs(featA[LK_F_A] - featB[LK_F_A]) 771 ax[LK_AX_TB] = lkh_abs(featA[LK_F_B] - featB[LK_F_B]) 772 ax[LK_AXR_MEAS + LK_AX_TL] = 1 773 ax[LK_AXR_MEAS + LK_AX_TA] = 1 774 ax[LK_AXR_MEAS + LK_AX_TB] = 1 775 } } 776 return 0 777} 778 779// ---- the band --------------------------------------------------------------------------------------------------------------- 780// every pair of references: per axis the worst distance (the band), the mean, the pair that set the worst; returns the pair count 781func lk_band(imgs: *i64, feats: *i64, n: i64, c: i64, band: *i64) -> i64 { 782 var k: i64 = 0 783 while k < LK_B_N { band[k] = 0; k = k + 1 } 784 let ax: *i64 = sys_mmap(LK_AXR_N * LK_I64) as *i64 785 let sums: *i64 = sys_mmap(LK_AX_N * LK_I64) as *i64 786 k = 0 787 while k < LK_AX_N { sums[k] = 0; k = k + 1 } 788 var pairs: i64 = 0 789 var i: i64 = 0 790 while i < n { 791 var j: i64 = i + 1 792 while j < n { 793 lk_axes(imgs[i] as *i64, feats[i] as *i64, imgs[j] as *i64, feats[j] as *i64, c, ax) 794 var a: i64 = 0 795 while a < LK_AX_N { 796 if ax[LK_AXR_MEAS + a] == 1 { 797 if band[LK_B_PAIRS + a] == 0 { band[LK_B_MAX + a] = ax[a]; band[LK_B_PI + a] = i; band[LK_B_PJ + a] = j } 798 if ax[a] > band[LK_B_MAX + a] { band[LK_B_MAX + a] = ax[a]; band[LK_B_PI + a] = i; band[LK_B_PJ + a] = j } 799 sums[a] = sums[a] + ax[a] 800 band[LK_B_PAIRS + a] = band[LK_B_PAIRS + a] + 1 801 } 802 a = a + 1 803 } 804 pairs = pairs + 1 805 j = j + 1 806 } 807 i = i + 1 808 } 809 k = 0 810 while k < LK_AX_N { if band[LK_B_PAIRS + k] > 0 { band[LK_B_MEAN + k] = sums[k] / band[LK_B_PAIRS + k] } k = k + 1 } 811 band[LK_B_TOTAL] = pairs 812 return pairs 813} 814// one subject against the reference set: per axis the nearest reference and whether it lies within the band 815func lk_score_img(sub: *i64, subf: *i64, imgs: *i64, feats: *i64, n: i64, c: i64, band: *i64, sc: *i64) -> i64 { 816 var k: i64 = 0 817 while k < LK_S_N { sc[k] = 0; k = k + 1 } 818 let ax: *i64 = sys_mmap(LK_AXR_N * LK_I64) as *i64 819 let seen: *i64 = sys_mmap(LK_AX_N * LK_I64) as *i64 820 k = 0 821 while k < LK_AX_N { seen[k] = 0; k = k + 1 } 822 var i: i64 = 0 823 while i < n { 824 lk_axes(sub, subf, imgs[i] as *i64, feats[i] as *i64, c, ax) 825 var a: i64 = 0 826 while a < LK_AX_N { 827 if ax[LK_AXR_MEAS + a] == 1 { 828 if seen[a] == 0 { sc[LK_S_DIST + a] = ax[a]; sc[LK_S_NEAR + a] = i; seen[a] = 1 } 829 if ax[a] < sc[LK_S_DIST + a] { sc[LK_S_DIST + a] = ax[a]; sc[LK_S_NEAR + a] = i } 830 } 831 a = a + 1 832 } 833 i = i + 1 834 } 835 var a: i64 = 0 836 while a < LK_AX_N { 837 var st: i64 = LK_ST_UNMEASURED 838 if seen[a] == 1 { if band[LK_B_PAIRS + a] > 0 { 839 if sc[LK_S_DIST + a] <= band[LK_B_MAX + a] { st = LK_ST_INSIDE } else { st = LK_ST_OUTSIDE } 840 } } 841 sc[LK_S_STATE + a] = st 842 if st == LK_ST_INSIDE { sc[LK_S_INSIDE] = sc[LK_S_INSIDE] + 1 } 843 if st == LK_ST_OUTSIDE { sc[LK_S_OUTSIDE] = sc[LK_S_OUTSIDE] + 1 } 844 if st == LK_ST_UNMEASURED { sc[LK_S_UNMEASURED] = sc[LK_S_UNMEASURED] + 1 } 845 a = a + 1 846 } 847 var v: i64 = LK_V_UNMEASURED 848 if sc[LK_S_OUTSIDE] > 0 { v = LK_V_OUTSIDE } else { if sc[LK_S_INSIDE] > 0 { v = LK_V_INSIDE } } 849 if band[LK_B_TOTAL] == 0 { v = LK_V_NOBAND } 850 sc[LK_S_VERDICT] = v 851 return v 852} 853 854// ---- the reference list ----------------------------------------------------------------------------------------------------- 855// a list file: one path per line, blank lines and lines starting with the comment byte skipped, an optional `@oracle <path>` directive; 856// a file that opens with a PNG or JPEG signature is a list of one. Returns the path array (NUL-terminated copies as i64), out_n the 857// count, or 0 with out_n = -1 when the file cannot be read 858func lk_list_read(ctx: *i64, path: *u8, out_n: *i64) -> *i64 { 859 let fl: *i64 = sys_mmap(2 * LK_I64) as *i64 860 fl[0] = 0 861 let buf: *u8 = sys_read_file(path, fl) 862 if (buf as i64) == 0 { out_n[0] = 0 - 1; return 0 as *i64 } 863 let n: i64 = fl[0] 864 if n <= 0 { out_n[0] = 0 - 1; return 0 as *i64 } 865 let first: i64 = (buf[0] as i64) & LK_BYTE 866 var single: i64 = 0 867 if first == LK_PNG_SIG0 { single = 1 } 868 if first == LK_JPEG_SIG0 { single = 1 } 869 if single == 1 { 870 let one: *i64 = sys_mmap(LK_I64) as *i64 871 one[0] = lkh_span_copy(path, 0, lkh_slen(path)) as i64 872 out_n[0] = 1 873 return one 874 } 875 var lines: i64 = 0 876 var ls: i64 = 0 877 while ls < n { let le: i64 = lkh_line_end(buf, ls, n); lines = lines + 1; ls = le + 1 } 878 let arr: *i64 = sys_mmap((lines + 1) * LK_I64) as *i64 879 var count: i64 = 0 880 let dlen: i64 = lkh_slen(LK_DIRECTIVE_ORACLE) 881 ls = 0 882 while ls < n { 883 var le: i64 = lkh_line_end(buf, ls, n) 884 var e: i64 = le 885 var go: i64 = 1 886 while go == 1 { 887 if e <= ls { go = 0 } else { 888 let cb: i64 = (buf[e - 1] as i64) & LK_BYTE 889 var ws: i64 = 0 890 if cb == LK_CR { ws = 1 } 891 if cb == LK_SPACE { ws = 1 } 892 if cb == LK_TAB { ws = 1 } 893 if ws == 1 { e = e - 1 } else { go = 0 } 894 } 895 } 896 if e > ls { if ((buf[ls] as i64) & LK_BYTE) != LK_HASH { 897 var isdir: i64 = 0 898 if e - ls > dlen { 899 isdir = 1 900 var i: i64 = 0 901 while i < dlen { if buf[ls + i] != LK_DIRECTIVE_ORACLE[i] { isdir = 0 } i = i + 1 } 902 } 903 if isdir == 1 { 904 // the list's directive names the oracle unless the caller already did (a CLI argument wins over the list) 905 if ctx[LK_C_OPATH] == 0 { lk_ctx_oracle(ctx, lkh_span_copy(buf, ls + dlen, e)) } 906 } else { 907 arr[count] = lkh_span_copy(buf, ls, e) as i64 908 count = count + 1 909 } 910 } } 911 ls = le + 1 912 } 913 out_n[0] = count 914 return arr 915} 916 917// ---- the receipts ----------------------------------------------------------------------------------------------------------- 918func lk_print_img(label: *u8, path: *u8, img: *i64) -> i64 { 919 pm_puts("LIKENESS " as *u8); pm_puts(label); pm_puts("=" as *u8); pm_puts(path) 920 pm_puts(" align=" as *u8); pm_puts(lk_align_name(img[LK_I_ALIGN])) 921 pm_kv(" w=" as *u8, img[LK_I_W]); pm_kv(" h=" as *u8, img[LK_I_H]); pm_kv(" codec=" as *u8, img[LK_I_CODEC]) 922 pm_kv(" iod_decipx=" as *u8, img[LK_I_IOD]); pm_kv(" eyes_landmarked=" as *u8, img[LK_I_EYES]); pm_kv(" mouth=" as *u8, img[LK_I_MOUTH]) 923 pm_kv(" tilt_ok=" as *u8, img[LK_I_TILT_OK]); pm_kv(" tilt_R_deg10=" as *u8, img[LK_I_TILT_R]); pm_kv(" tilt_L_deg10=" as *u8, img[LK_I_TILT_L]) 924 pm_kv(" eye_R=" as *u8, img[LK_I_ERX]); pm_kv("," as *u8, img[LK_I_ERY]); pm_kv(" eye_L=" as *u8, img[LK_I_ELX]); pm_kv("," as *u8, img[LK_I_ELY]) 925 pm_kv(" ruler_route=" as *u8, img[LK_I_ROUTE]); pm_kv(" pair_fail=" as *u8, img[LK_I_PAIRFAIL]); pm_kv(" upsampled=" as *u8, img[LK_I_UPSAMPLED]) 926 pm_puts("\n" as *u8) 927 return 0 928} 929func lk_print_feat(path: *u8, feat: *i64) -> i64 { 930 pm_puts("LIKENESS features=" as *u8); pm_puts(path) 931 pm_kv(" skin_pixels=" as *u8, feat[LK_F_SKIN_N]); pm_kv(" skin_L_e3=" as *u8, feat[LK_F_L]); pm_kv(" skin_a_e3=" as *u8, feat[LK_F_A]); pm_kv(" skin_b_e3=" as *u8, feat[LK_F_B]) 932 pm_kv(" grid=" as *u8, feat[LK_F_GRID]); pm_kv(" texture_energy_per_px=" as *u8, feat[LK_F_TEXE]) 933 pm_puts("\n" as *u8) 934 return 0 935} 936func lk_print_canvas(c: i64, min_iod: i64, min_path: *u8, nrefs: i64) -> i64 { 937 pm_puts("LIKENESS canvas" as *u8) 938 pm_kv(" ruler_version=" as *u8, lk_ruler_version()) 939 pm_kv(" side_px=" as *u8, c); pm_kv(" eye_span_decipx=" as *u8, lk_canvas_span(c)); pm_kv(" eye_R_x=" as *u8, lk_canvas_eye_rx(c)); pm_kv(" eye_L_x=" as *u8, lk_canvas_eye_lx(c)); pm_kv(" eye_y=" as *u8, lk_canvas_eye_y(c)) 940 pm_kv(" derived_from_smallest_reference_iod_decipx=" as *u8, min_iod); pm_puts(" of=" as *u8); pm_puts(min_path); pm_kv(" references=" as *u8, nrefs) 941 pm_kv(" spans_wide=" as *u8, LK_CANVAS_SPANS); pm_kv(" grid=" as *u8, VD_GRID); pm_kv(" sobel_floor_px=" as *u8, VD_GRID * LK_SOBEL_CELL) 942 pm_kv(" eye_y_permil=" as *u8, LK_EYE_Y_PERMIL); pm_puts(" eye_y_basis=" as *u8); pm_puts(LK_EYE_Y_BASIS); pm_kv(" brow_above_eyeline_permil_of_span=" as *u8, LK_EYE_Y_BASIS_BROW_PERMIL) 943 pm_puts("\n" as *u8) 944 return 0 945} 946func lk_print_band(band: *i64, paths: *i64) -> i64 { 947 var a: i64 = 0 948 while a < LK_AX_N { 949 pm_puts("LIKENESS band axis=" as *u8); pm_puts(lk_axis_name(a)) 950 pm_kv(" pairs=" as *u8, band[LK_B_PAIRS + a]); pm_kv(" of=" as *u8, band[LK_B_TOTAL]) 951 pm_kv(" band_max=" as *u8, band[LK_B_MAX + a]); pm_kv(" mean=" as *u8, band[LK_B_MEAN + a]) 952 if band[LK_B_PAIRS + a] > 0 { 953 pm_puts(" worst_pair=" as *u8); pm_puts(lkh_basename(paths[band[LK_B_PI + a]] as *u8)); pm_puts("," as *u8); pm_puts(lkh_basename(paths[band[LK_B_PJ + a]] as *u8)) 954 } else { pm_puts(" worst_pair=UNMEASURED" as *u8) } 955 pm_puts("\n" as *u8) 956 a = a + 1 957 } 958 return 0 959} 960func lk_print_score(path: *u8, sc: *i64, band: *i64, paths: *i64) -> i64 { 961 var a: i64 = 0 962 while a < LK_AX_N { 963 pm_puts("LIKENESS subject=" as *u8); pm_puts(lkh_basename(path)); pm_puts(" axis=" as *u8); pm_puts(lk_axis_name(a)) 964 pm_kv(" dist=" as *u8, sc[LK_S_DIST + a]); pm_kv(" band=" as *u8, band[LK_B_MAX + a]) 965 pm_puts(" nearest=" as *u8) 966 if sc[LK_S_STATE + a] == LK_ST_UNMEASURED { pm_puts("-" as *u8) } else { pm_puts(lkh_basename(paths[sc[LK_S_NEAR + a]] as *u8)) } 967 pm_puts(" state=" as *u8); pm_puts(lk_state_name(sc[LK_S_STATE + a])) 968 pm_puts("\n" as *u8) 969 a = a + 1 970 } 971 return 0 972} 973func lk_print_verdict(path: *u8, sc: *i64) -> i64 { 974 pm_puts("LIKENESS subject=" as *u8); pm_puts(lkh_basename(path)) 975 pm_kv(" axes_inside=" as *u8, sc[LK_S_INSIDE]); pm_kv(" axes_outside=" as *u8, sc[LK_S_OUTSIDE]); pm_kv(" axes_unmeasured=" as *u8, sc[LK_S_UNMEASURED]); pm_kv(" of=" as *u8, LK_AX_N) 976 pm_puts(" verdict=" as *u8); pm_puts(lk_verdict_name(sc[LK_S_VERDICT])); pm_puts("\n" as *u8) 977 return 0 978} 979 980// ---- THE CONTRACT: the reference set loaded, aligned, banded; a subject scored and printed ---------------------------------- 981// the set record handed back by lk_refs_load 982const LK_R_IMGS: i64 = 0 983const LK_R_FEATS: i64 = 1 984const LK_R_PATHS: i64 = 2 985const LK_R_N: i64 = 3 986const LK_R_LISTED: i64 = 4 987const LK_R_UNREADABLE: i64 = 5 988const LK_R_UNDECODABLE: i64 = 6 989const LK_R_UNALIGNED: i64 = 7 990const LK_R_CANVAS: i64 = 8 991const LK_R_MIN_IOD: i64 = 9 992const LK_R_MIN_PATH: i64 = 10 993const LK_R_BAND: i64 = 11 994const LK_R_PAIRS: i64 = 12 995const LK_R_ORACLE_BYTES: i64 = 13 996const LK_R_GRID: i64 = 14 997const LK_R_GEOM_BAND: i64 = 15 998const LK_R_GEOM_PAIRS: i64 = 16 999const LK_R_SLOTS: i64 = 17 1000// load every reference in the list, keep the aligned ones, derive the canvas and the band; returns the aligned count, or -1 when the 1001// list cannot be read. Prints the receipts. 1002func lk_refs_load(ctx: *i64, list: *u8, set: *i64) -> i64 { 1003 var k: i64 = 0 1004 while k < LK_R_SLOTS { set[k] = 0; k = k + 1 } 1005 let cnt: *i64 = sys_mmap(LK_I64) as *i64 1006 let paths: *i64 = lk_list_read(ctx, list, cnt) 1007 if cnt[0] < 0 { pm_puts("LIKENESS list=" as *u8); pm_puts(list); pm_puts(" status=UNREADABLE\n" as *u8); return 0 - 1 } 1008 set[LK_R_LISTED] = cnt[0] 1009 set[LK_R_ORACLE_BYTES] = ctx[LK_C_OLEN] 1010 pm_puts("LIKENESS list=" as *u8); pm_puts(list); pm_kv(" listed=" as *u8, cnt[0]); pm_kv(" oracle_bytes=" as *u8, ctx[LK_C_OLEN]); pm_kv(" cascades_loaded=" as *u8, lk_ctx_cascades(ctx)); pm_puts("\n" as *u8) 1011 let imgs: *i64 = sys_mmap((cnt[0] + 1) * LK_I64) as *i64 1012 let feats: *i64 = sys_mmap((cnt[0] + 1) * LK_I64) as *i64 1013 let kept: *i64 = sys_mmap((cnt[0] + 1) * LK_I64) as *i64 1014 var n: i64 = 0 1015 var i: i64 = 0 1016 while i < cnt[0] { 1017 let p: *u8 = paths[i] as *u8 1018 let img: *i64 = lk_img_new() 1019 let rc: i64 = lk_load(ctx, p, img) 1020 if rc == LK_OK { 1021 imgs[n] = img as i64 1022 kept[n] = p as i64 1023 n = n + 1 1024 lk_print_img("reference" as *u8, p, img) 1025 } else { 1026 pm_puts("LIKENESS reference=" as *u8); pm_puts(p) 1027 if rc == LK_E_READ { set[LK_R_UNREADABLE] = set[LK_R_UNREADABLE] + 1; pm_puts(" status=UNREADABLE\n" as *u8) } 1028 if rc == LK_E_DECODE { set[LK_R_UNDECODABLE] = set[LK_R_UNDECODABLE] + 1; pm_puts(" status=UNDECODABLE\n" as *u8) } 1029 if rc == LK_E_UNALIGNED { set[LK_R_UNALIGNED] = set[LK_R_UNALIGNED] + 1; pm_puts(" status=UNALIGNED (no oracle row, no eye pair, no candidates)\n" as *u8) } 1030 } 1031 i = i + 1 1032 } 1033 set[LK_R_IMGS] = imgs as i64 1034 set[LK_R_FEATS] = feats as i64 1035 set[LK_R_PATHS] = kept as i64 1036 set[LK_R_N] = n 1037 pm_puts("LIKENESS references" as *u8); pm_kv(" listed=" as *u8, cnt[0]); pm_kv(" aligned=" as *u8, n); pm_kv(" unreadable=" as *u8, set[LK_R_UNREADABLE]); pm_kv(" undecodable=" as *u8, set[LK_R_UNDECODABLE]); pm_kv(" unaligned=" as *u8, set[LK_R_UNALIGNED]) 1038 pm_kv(" partition_sums=" as *u8, (n + set[LK_R_UNREADABLE] + set[LK_R_UNDECODABLE] + set[LK_R_UNALIGNED] == cnt[0]) as i64) 1039 pm_puts("\n" as *u8) 1040 if n == 0 { return 0 } 1041 var min_iod: i64 = 0 1042 var min_i: i64 = 0 1043 i = 0 1044 while i < n { 1045 let img: *i64 = imgs[i] as *i64 1046 if i == 0 { min_iod = img[LK_I_IOD] } 1047 if img[LK_I_IOD] < min_iod { min_iod = img[LK_I_IOD]; min_i = i } 1048 i = i + 1 1049 } 1050 let c: i64 = lk_canvas(min_iod) 1051 set[LK_R_CANVAS] = c 1052 set[LK_R_MIN_IOD] = min_iod 1053 set[LK_R_MIN_PATH] = kept[min_i] 1054 lk_print_canvas(c, min_iod, kept[min_i] as *u8, n) 1055 // the geometry band first (landmarks only): it sets the cell size every grid on this set is compared at 1056 let gp: *i64 = sys_mmap(LK_I64) as *i64 1057 let gband: i64 = lk_band_geometry(imgs, n, c, gp) 1058 let grid: i64 = lk_grid_from_band(c, gband) 1059 set[LK_R_GEOM_BAND] = gband 1060 set[LK_R_GEOM_PAIRS] = gp[0] 1061 set[LK_R_GRID] = grid 1062 pm_puts("LIKENESS grid" as *u8); pm_kv(" cells_per_side=" as *u8, grid); pm_kv(" cell_px=" as *u8, c / grid); pm_kv(" derived_from_geometry_band_permil=" as *u8, gband); pm_kv(" geometry_pairs=" as *u8, gp[0]); pm_kv(" sobel_floor_px=" as *u8, LK_SOBEL_CELL); pm_puts("\n" as *u8) 1063 i = 0 1064 while i < n { 1065 let f: *i64 = lk_feat_new() 1066 lk_features(imgs[i] as *i64, c, grid, f) 1067 feats[i] = f as i64 1068 lk_print_feat(kept[i] as *u8, f) 1069 i = i + 1 1070 } 1071 let band: *i64 = sys_mmap(LK_B_N * LK_I64) as *i64 1072 set[LK_R_PAIRS] = lk_band(imgs, feats, n, c, band) 1073 set[LK_R_BAND] = band as i64 1074 pm_puts("LIKENESS band" as *u8); pm_kv(" references=" as *u8, n); pm_kv(" pairs=" as *u8, set[LK_R_PAIRS]); pm_puts(" rule=max-over-same-person-pairs\n" as *u8) 1075 lk_print_band(band, kept) 1076 return n 1077} 1078// score one loaded set member or subject record: features on the set's canvas, then the nearest-reference test per axis 1079func lk_score_subject(set: *i64, path: *u8, sub: *i64, sc: *i64) -> i64 { 1080 let f: *i64 = lk_feat_new() 1081 lk_features(sub, set[LK_R_CANVAS], set[LK_R_GRID], f) 1082 lk_print_img("subject" as *u8, path, sub) 1083 lk_print_feat(path, f) 1084 let v: i64 = lk_score_img(sub, f, set[LK_R_IMGS] as *i64, set[LK_R_FEATS] as *i64, set[LK_R_N], set[LK_R_CANVAS], set[LK_R_BAND] as *i64, sc) 1085 lk_print_score(path, sc, set[LK_R_BAND] as *i64, set[LK_R_PATHS] as *i64) 1086 return v 1087} 1088// lk_score: the whole door -- the reference list loaded and banded, the subject decoded and aligned, every axis published, the 1089// verdict LAST. Returns LK_V_INSIDE, LK_V_OUTSIDE, LK_V_UNMEASURED, LK_V_NOBAND, or the negative load status of the subject/list. 1090func lk_score(ctx: *i64, subject: *u8, list: *u8, sc: *i64) -> i64 { 1091 let set: *i64 = sys_mmap(LK_R_SLOTS * LK_I64) as *i64 1092 let n: i64 = lk_refs_load(ctx, list, set) 1093 if n < 0 { return LK_E_READ } 1094 let sub: *i64 = lk_img_new() 1095 let rc: i64 = lk_load(ctx, subject, sub) 1096 if rc != LK_OK { 1097 pm_puts("LIKENESS subject=" as *u8); pm_puts(subject) 1098 if rc == LK_E_READ { pm_puts(" status=UNREADABLE verdict=UNMEASURED\n" as *u8); return LK_E_READ } 1099 if rc == LK_E_DECODE { pm_puts(" status=UNDECODABLE verdict=UNMEASURED\n" as *u8); return LK_E_DECODE } 1100 pm_puts(" status=UNALIGNED verdict=UNMEASURED\n" as *u8) 1101 return LK_E_UNALIGNED 1102 } 1103 if n == 0 { 1104 var k: i64 = 0 1105 while k < LK_S_N { sc[k] = 0; k = k + 1 } 1106 sc[LK_S_VERDICT] = LK_V_NOBAND 1107 lk_print_img("subject" as *u8, subject, sub) 1108 pm_puts("LIKENESS subject=" as *u8); pm_puts(lkh_basename(subject)); pm_puts(" references_aligned=0 verdict=NO-BAND\n" as *u8) 1109 return LK_V_NOBAND 1110 } 1111 let v: i64 = lk_score_subject(set, subject, sub, sc) 1112 lk_print_verdict(subject, sc) 1113 return v 1114}