code wiki / (root) / nx_imgsearch_engine.nx

nx_imgsearch_engine.nx source

↩ module page · 476 lines · 22067 B

1// nx_imgsearch_engine.nx -- THE MULTI-TIER REVERSE-IMAGE QUERY ENGINE. 2// 3// Replaces the shipped single-tier client (nx_image_search) on two counts, both of them load-bearing: 4// 5// 1. IT SCALES. The shipped client ranks by scanning every fingerprint and then SELECTION-SORTING the 6// whole corpus -- O(N^2). At 1,000 images that is a million comparisons; at the 10^6 an indie image 7// index reaches it is 10^12 and the query never returns. Here every tier extracts its top-K through 8// a BOUNDED MAX-HEAP of size K: O(N log K) time and O(K) space, with N touched exactly once. Going 9// from 10^3 to 10^6 images multiplies the work by 10^3, not 10^6. 10// 11// 2. IT FUSES. One descriptor cannot cover the modification space (nx_imgbench proves it class by 12// class), so the engine runs every registered tier and combines their ranked lists with RECIPROCAL 13// RANK FUSION -- score = sum over tiers of weight / (K_RRF + rank). RRF is the fusion the S-Class IR 14// literature settles on precisely because it needs no score calibration between rankers: a Hamming 15// distance over 64 bits and an L1 distance over 80 dimensions are never comparable as magnitudes, 16// but their RANKS always are. Same integer discipline as the text engine -- no floats anywhere. 17// 18// The engine holds only base-class tier pointers. Adding local-feature or semantic matching is a new 19// nx_imgtier subtype plus one add_tier call; this file does not change. That is the design contract. 20// 21// genealogy_id: cormack_2009_reciprocal_rank_fusion + williams_1975_bounded_heap_selection 22// license_tier: ORIGINAL 23import "nx_imgsearch_tier.nx" 24import "nx_phash_index.nx" 25const NX_MAGIC_1000000: i64 = 1000000 26 27const NX_IE_MAXTIERS: i64 = 8 28const NX_IE_MAXK: i64 = 64 29const NX_IE_RRF_K: i64 = 60 // Cormack/Clarke/Buettcher 2009 -- damps the top-rank monopoly 30 31// Per-tier Hamming ACCELERATION. A hash tier (a single 64-bit fingerprint per image) can generate its 32// within-threshold candidates SUBLINEARLY via a BK-tree (nx_phash_index) instead of scanning all N. 33// The BK-tree returns the EXACT within-radius set by the triangle inequality -- no recall loss -- while 34// touching a small fraction of the corpus on clustered data. Without this, a 10M-image index is a 35// 10M-comparison scan PER TIER PER QUERY; with it, candidate generation is what actually scales. Non- 36// hash tiers (the 80-dim similarity tier) leave active=0 and fall back to the linear heap scan (their 37// scale path is an ANN graph -- nx_vec_nsw, filed seq717). 38struct nx_bkaccel { 39 active: i64, // 1 if this tier is BK-accelerated 40 fp: i64, // *i64 : node fingerprints 41 pay: i64, // *i64 : node payloads (image indices) 42 ch: i64, // *i64 : 65 child slots per node 43 n: i64, // node count 44} 45const NX_BKACCEL_BYTES: i64 = 40 46 47struct nx_imgengine { 48 ntiers: i64, 49 tiers: i64, // *i64 : tier pointers 50 cap: i64, 51 count: i64, 52 descs: i64, // *i64 : per-tier descriptor block pointers 53 pays: i64, // *i64 : caller payload (docid / cid) per indexed image 54 accel: i64, // *i64 : per-tier *nx_bkaccel (0 = not accelerated) 55 last_visited: i64, // BK comparisons made by the most recent accelerated tier_topk (for the ruler) 56} 57 58const NX_IMGENGINE_BYTES: i64 = 64 59 60func nx_imgengine_count(e: *nx_imgengine) -> i64 { return e.count } 61func nx_imgengine_cap(e: *nx_imgengine) -> i64 { return e.cap } 62func nx_imgengine_ntiers(e: *nx_imgengine) -> i64 { return e.ntiers } 63func nx_imgengine_last_visited(e: *nx_imgengine) -> i64 { return e.last_visited } 64func nx_imgengine_tier(e: *nx_imgengine, i: i64) -> *nx_imgtier { 65 let arr: *i64 = e.tiers as *i64 66 return arr[i] as *nx_imgtier 67} 68 69func nx_imgengine_new(cap: i64) -> *nx_imgengine { 70 let e: *nx_imgengine = sys_mmap(NX_IMGENGINE_BYTES) as *nx_imgengine 71 e.ntiers = 0 72 e.tiers = sys_mmap(8 * NX_IE_MAXTIERS) as i64 73 e.cap = cap 74 e.count = 0 75 e.descs = sys_mmap(8 * NX_IE_MAXTIERS) as i64 76 e.pays = sys_mmap(8 * cap) as i64 77 e.accel = sys_mmap(8 * NX_IE_MAXTIERS) as i64 78 var z: i64 = 0 79 let aarr: *i64 = e.accel as *i64 80 while z < NX_IE_MAXTIERS { aarr[z] = 0; z = z + 1 } 81 e.last_visited = 0 82 return e 83} 84 85func ie_accel(e: *nx_imgengine, ti: i64) -> *nx_bkaccel { 86 let aarr: *i64 = e.accel as *i64 87 return aarr[ti] as *nx_bkaccel 88} 89 90// a hash tier stores exactly one 64-bit fingerprint per image (idx_dim==1) and ranks by Hamming, so 91// the BK-tree metric index applies. That is the copy and orientation tiers. 92func ie_tier_is_hash(t: *nx_imgtier) -> i64 { 93 if nx_imgtier_idx_dim(t) == 1 { 94 if nx_imgtier_kind(t) == NX_IT_KIND_COPY { return 1 } 95 if nx_imgtier_kind(t) == NX_IT_KIND_ORIENT { return 1 } 96 } 97 return 0 98} 99 100// register a tier. Its descriptor block is allocated here from the tier's own declared idx_dim, so a 101// new subtype never needs the engine to know its width. A hash tier also gets a BK-tree. Returns the 102// tier slot, or -1 if full. 103func nx_imgengine_add_tier(e: *nx_imgengine, t: *nx_imgtier) -> i64 { 104 if e.ntiers >= NX_IE_MAXTIERS { return 0 - 1 } 105 let slot: i64 = e.ntiers 106 let tarr: *i64 = e.tiers as *i64 107 let darr: *i64 = e.descs as *i64 108 let aarr: *i64 = e.accel as *i64 109 tarr[slot] = t as i64 110 darr[slot] = sys_mmap(8 * e.cap * nx_imgtier_idx_dim(t)) as i64 111 if ie_tier_is_hash(t) == 1 { 112 let ac: *nx_bkaccel = sys_mmap(NX_BKACCEL_BYTES) as *nx_bkaccel 113 ac.active = 1 114 ac.fp = sys_mmap(8 * e.cap) as i64 115 ac.pay = sys_mmap(8 * e.cap) as i64 116 ac.ch = sys_mmap(8 * e.cap * PI_SLOTS) as i64 117 pi_init_children(ac.ch as *i64, e.cap) 118 ac.n = 0 119 aarr[slot] = ac as i64 120 } else { 121 aarr[slot] = 0 122 } 123 e.ntiers = slot + 1 124 return slot 125} 126 127// descriptor slot of image `i` for tier `ti` 128func ie_desc_at(e: *nx_imgengine, ti: i64, i: i64) -> *i64 { 129 let darr: *i64 = e.descs as *i64 130 let t: *nx_imgtier = nx_imgengine_tier(e, ti) 131 let d: i64 = nx_imgtier_idx_dim(t) 132 return (darr[ti] + 8 * i * d) as *i64 133} 134 135// INGEST: describe one image with every registered tier and store it. rgb may be 0 for tiers that 136// only need luminance. Returns the new corpus count, unchanged if the engine is full (bounded, never 137// a silent overwrite). 138func nx_imgengine_add_image(e: *nx_imgengine, gray: *u8, rgb: *u8, w: i64, h: i64, payload: i64) -> i64 { 139 if e.count >= e.cap { return e.count } 140 let i: i64 = e.count 141 var ti: i64 = 0 142 while ti < e.ntiers { 143 let t: *nx_imgtier = nx_imgengine_tier(e, ti) 144 let slot: *i64 = ie_desc_at(e, ti, i) 145 nx_imgtier_describe_index(t, gray, rgb, w, h, slot) 146 // mirror a hash tier's fingerprint into its BK-tree (payload = image index, so a radius query 147 // returns image indices directly). Incremental insert -- no separate build phase. 148 let ac: *nx_bkaccel = ie_accel(e, ti) 149 if ac != (0 as *nx_bkaccel) { if ac.active == 1 { 150 ac.n = pi_insert(ac.fp as *i64, ac.pay as *i64, ac.ch as *i64, ac.n, slot[0], i) 151 } } 152 ti = ti + 1 153 } 154 let parr: *i64 = e.pays as *i64 155 parr[i] = payload 156 e.count = i + 1 157 return e.count 158} 159 160// ---- bounded max-heap: keeps the K SMALLEST keys seen, in O(log K) per candidate ---- 161func ie_swap(hd: *i64, hp: *i64, a: i64, b: i64) -> i64 { 162 let td: i64 = hd[a]; hd[a] = hd[b]; hd[b] = td 163 let tp: i64 = hp[a]; hp[a] = hp[b]; hp[b] = tp 164 return 0 165} 166func ie_heap_up(hd: *i64, hp: *i64, start: i64) -> i64 { 167 var i: i64 = start 168 var go: i64 = 1 169 while go == 1 { 170 if i <= 0 { go = 0 } else { 171 let p: i64 = (i - 1) / 2 172 if hd[p] < hd[i] { ie_swap(hd, hp, p, i); i = p } else { go = 0 } 173 } 174 } 175 return 0 176} 177func ie_heap_down(hd: *i64, hp: *i64, n: i64, start: i64) -> i64 { 178 var i: i64 = start 179 var go: i64 = 1 180 while go == 1 { 181 let l: i64 = 2 * i + 1 182 let r: i64 = l + 1 183 var m: i64 = i 184 if l < n { if hd[l] > hd[m] { m = l } } 185 if r < n { if hd[r] > hd[m] { m = r } } 186 if m == i { go = 0 } else { ie_swap(hd, hp, m, i); i = m } 187 } 188 return 0 189} 190// offer candidate (key,item); nbox[0] holds the current heap size. Root is always the WORST kept. 191func ie_heap_offer(hd: *i64, hp: *i64, nbox: *i64, k: i64, key: i64, item: i64) -> i64 { 192 let n: i64 = nbox[0] 193 if n < k { 194 hd[n] = key; hp[n] = item 195 nbox[0] = n + 1 196 ie_heap_up(hd, hp, n) 197 return 1 198 } 199 if key < hd[0] { 200 hd[0] = key; hp[0] = item 201 ie_heap_down(hd, hp, n, 0) 202 return 1 203 } 204 return 0 205} 206// drain the heap into out_* sorted ASCENDING by key (pop-max fills from the back). Returns the count. 207func ie_heap_drain(hd: *i64, hp: *i64, n_in: i64, out_key: *i64, out_item: *i64) -> i64 { 208 var n: i64 = n_in 209 var w: i64 = n_in - 1 210 while n > 0 { 211 out_key[w] = hd[0] 212 out_item[w] = hp[0] 213 n = n - 1 214 hd[0] = hd[n]; hp[0] = hp[n] 215 ie_heap_down(hd, hp, n, 0) 216 w = w - 1 217 } 218 return n_in 219} 220 221// ACCELERATED hash-tier candidate generation: query the BK-tree at radius = tier threshold for each 222// of the query's fingerprints (1 for copy, 8 for orient), UNION the returned image indices (dedup via 223// a seen bitmap), then rank ONLY those candidates with the bounded heap. Returns the within-threshold 224// set nearest-first -- EXACTLY the set a linear scan would flag as confident, by the BK-tree's 225// triangle-inequality exactness, but touching a sublinear fraction of the corpus. e.last_visited 226// records the comparisons made (the ruler prints it against N). A hash tier legitimately returns only 227// within-threshold items: it has no business ranking images it considers non-matches -- the similarity 228// tier owns the "looks like" fallback when nothing is confident. 229func ie_tier_topk_bk(e: *nx_imgengine, ti: i64, qdesc: *i64, k: i64, 230 out_item: *i64, out_dist: *i64) -> i64 { 231 let t: *nx_imgtier = nx_imgengine_tier(e, ti) 232 let ac: *nx_bkaccel = ie_accel(e, ti) 233 let radius: i64 = nx_imgtier_threshold(t) 234 let nq: i64 = nx_imgtier_qry_dim(t) // 1 (copy) or 8 (orient) 235 236 var kk: i64 = k 237 if kk > NX_IE_MAXK { kk = NX_IE_MAXK } 238 if kk < 1 { kk = 1 } 239 let hd: *i64 = sys_mmap(8 * kk) as *i64 240 let hp: *i64 = sys_mmap(8 * kk) as *i64 241 let nbox: *i64 = sys_mmap(8) as *i64 242 nbox[0] = 0 243 244 let seen: *u8 = sys_mmap(e.count + 8) 245 var z: i64 = 0 246 while z < e.count { seen[z] = 0 as u8; z = z + 1 } 247 let cbuf: *i64 = sys_mmap(8 * (e.count + 8)) as *i64 248 let visbox: *i64 = sys_mmap(8) as *i64 249 var visited: i64 = 0 250 251 var qh: i64 = 0 252 while qh < nq { 253 let nc: i64 = pi_query(ac.fp as *i64, ac.pay as *i64, ac.ch as *i64, ac.n, 254 qdesc[qh], radius, cbuf, e.count, visbox) 255 visited = visited + visbox[0] 256 var c: i64 = 0 257 while c < nc { 258 let img: i64 = cbuf[c] 259 if seen[img] == (0 as u8) { 260 seen[img] = 1 as u8 261 // full tier distance (min over the query's orientations for orient) on the candidate 262 let d: i64 = nx_imgtier_distance(t, qdesc, ie_desc_at(e, ti, img)) 263 ie_heap_offer(hd, hp, nbox, kk, d, img) 264 } 265 c = c + 1 266 } 267 qh = qh + 1 268 } 269 e.last_visited = visited 270 return ie_heap_drain(hd, hp, nbox[0], out_dist, out_item) 271} 272 273// TOP-K FOR ONE TIER: BK-accelerated for hash tiers, linear heap scan otherwise. out_item/out_dist 274// ascending by distance. Turns the per-tier scan from O(N) into sublinear candidate generation for the 275// tiers that dominate a reverse-image query. 276func nx_imgengine_tier_topk(e: *nx_imgengine, ti: i64, qdesc: *i64, k: i64, 277 out_item: *i64, out_dist: *i64) -> i64 { 278 let ac: *nx_bkaccel = ie_accel(e, ti) 279 if ac != (0 as *nx_bkaccel) { if ac.active == 1 { 280 return ie_tier_topk_bk(e, ti, qdesc, k, out_item, out_dist) 281 } } 282 var kk: i64 = k 283 if kk > NX_IE_MAXK { kk = NX_IE_MAXK } 284 if kk < 1 { kk = 1 } 285 let hd: *i64 = sys_mmap(8 * kk) as *i64 286 let hp: *i64 = sys_mmap(8 * kk) as *i64 287 let nbox: *i64 = sys_mmap(8) as *i64 288 nbox[0] = 0 289 let t: *nx_imgtier = nx_imgengine_tier(e, ti) 290 e.last_visited = e.count 291 var i: i64 = 0 292 while i < e.count { 293 let d: i64 = nx_imgtier_distance(t, qdesc, ie_desc_at(e, ti, i)) 294 ie_heap_offer(hd, hp, nbox, kk, d, i) 295 i = i + 1 296 } 297 return ie_heap_drain(hd, hp, nbox[0], out_dist, out_item) 298} 299 300// ---- THE QUERY: describe once per tier, top-K per tier, fuse by RRF ---- 301// out_pay -- caller payload of each result 302// out_score-- fused RRF score (larger is better; arbitrary integer units, comparable within one query) 303// out_tier -- which tier ranked this result best (so the caller can say HOW it matched) 304// out_dist -- that tier's raw distance to the result (Hamming for hash tiers, L1 for descriptor tiers) 305// Returns the number of results written (<= topk). 306// klass_filter: 0 = every registered tier votes; otherwise only tiers of that NX_IT_CLASS_* do. 307// Exists because "is this the same image?" and "what does this look like?" are different questions 308// and the tiers qualified to answer them are different sets (see nx_imgsearch_tier.nx). 309func nx_imgengine_query_class(e: *nx_imgengine, gray: *u8, rgb: *u8, w: i64, h: i64, topk: i64, 310 klass_filter: i64, 311 out_pay: *i64, out_score: *i64, out_tier: *i64, out_dist: *i64) -> i64 { 312 if e.count <= 0 { return 0 } 313 if e.ntiers <= 0 { return 0 } 314 var k: i64 = topk 315 if k > NX_IE_MAXK { k = NX_IE_MAXK } 316 if k < 1 { k = 1 } 317 318 // fused candidate pool: at most one entry per (tier, rank) 319 let pool: i64 = NX_IE_MAXTIERS * NX_IE_MAXK 320 let cand_item: *i64 = sys_mmap(8 * pool) as *i64 321 let cand_score: *i64 = sys_mmap(8 * pool) as *i64 // CONFIDENT score: tiers voting within threshold 322 let cand_raw: *i64 = sys_mmap(8 * pool) as *i64 // RAW score: every tier, threshold ignored 323 let cand_tier: *i64 = sys_mmap(8 * pool) as *i64 324 let cand_dist: *i64 = sys_mmap(8 * pool) as *i64 325 let cand_best: *i64 = sys_mmap(8 * pool) as *i64 // best normalised tier score, breaks RRF ties 326 var ncand: i64 = 0 327 328 let ti_item: *i64 = sys_mmap(8 * NX_IE_MAXK) as *i64 329 let ti_dist: *i64 = sys_mmap(8 * NX_IE_MAXK) as *i64 330 331 var ti: i64 = 0 332 while ti < e.ntiers { 333 let t: *nx_imgtier = nx_imgengine_tier(e, ti) 334 var eligible: i64 = 1 335 if klass_filter != 0 { if nx_imgtier_klass(t) != klass_filter { eligible = 0 } } 336 if eligible == 1 { 337 let qd: *i64 = sys_mmap(8 * nx_imgtier_qry_dim(t)) as *i64 338 nx_imgtier_describe_query(t, gray, rgb, w, h, qd) 339 // A tier also abstains when its OWN descriptor of the query is meaningless. Same principle 340 // as the threshold abstention above, applied one level earlier: a fingerprint with no 341 // structure in it is within distance 0 of every other structureless fingerprint, so its 342 // "match" is an artefact. It may still contribute to the RAW ordering (a caller asking 343 // "what does this most look like?" still gets an answer) but never to CONFIDENCE. 344 let qinfo: i64 = nx_imgtier_informative(t, qd) 345 let n: i64 = nx_imgengine_tier_topk(e, ti, qd, k, ti_item, ti_dist) 346 347 var r: i64 = 0 348 while r < n { 349 let item: i64 = ti_item[r] 350 let dist: i64 = ti_dist[r] 351 let contrib: i64 = nx_imgtier_weight(t) * 1000 / (NX_IE_RRF_K + r + 1) 352 let tscore: i64 = nx_imgtier_score(t, dist) 353 // ABSTENTION -- the correction that makes multi-tier fusion actually work. 354 // Plain RRF assumes every ranker is competent on every query. Ours are not, BY DESIGN: 355 // a hash tier is structurally blind to cropping, a layout tier is structurally blind to 356 // mirroring. Worse, the hash tiers are CORRELATED -- they share a descriptor, so when 357 // they are blind they are blind IDENTICALLY and their two wrong votes out-number the one 358 // tier that is right. Measured: fused crop-20 recall collapsed to 83 permille while the 359 // competent tier alone scored 937. So a tier's vote only counts as CONFIDENT when the 360 // candidate is inside that tier's own declared threshold -- i.e. when the tier is willing 361 // to call it a match. Out-of-competence tiers still contribute to the RAW score, which 362 // orders results only when nothing is confident (graceful degradation: "no match, but 363 // here is what it most looks like" rather than an empty answer). 364 var conf: i64 = 0 365 if qinfo == 1 { if dist <= nx_imgtier_threshold(t) { conf = contrib } } 366 var found: i64 = 0 - 1 367 var c: i64 = 0 368 while c < ncand { if cand_item[c] == item { found = c; c = ncand } else { c = c + 1 } } 369 if found >= 0 { 370 cand_score[found] = cand_score[found] + conf 371 cand_raw[found] = cand_raw[found] + contrib 372 if tscore > cand_best[found] { 373 cand_best[found] = tscore 374 cand_tier[found] = ti 375 cand_dist[found] = dist 376 } 377 } else { 378 if ncand < pool { 379 cand_item[ncand] = item 380 cand_score[ncand] = conf 381 cand_raw[ncand] = contrib 382 cand_tier[ncand] = ti 383 cand_dist[ncand] = dist 384 cand_best[ncand] = tscore 385 ncand = ncand + 1 386 } 387 } 388 r = r + 1 389 } 390 } 391 ti = ti + 1 392 } 393 394 // final top-K over the fused pool. Same bounded heap, keyed by NEGATED score so "smallest key" 395 // means "highest fused score" -- one heap implementation, no second comparator to keep in sync. 396 let hd: *i64 = sys_mmap(8 * k) as *i64 397 let hp: *i64 = sys_mmap(8 * k) as *i64 398 let nbox: *i64 = sys_mmap(8) as *i64 399 nbox[0] = 0 400 // Lexicographic key: CONFIDENT score dominates, RAW score breaks ties. Packing them into one 401 // integer keeps a single heap and a single comparator -- a second comparator is a second thing to 402 // keep in sync. Bounds are safe: contribution <= 1000*1000/61 ~ 16393 per tier, <= 8 tiers, so 403 // each component is < 2^17 and the packed key is < 2^38. 404 var c2: i64 = 0 405 while c2 < ncand { ie_heap_offer(hd, hp, nbox, k, 0 - (cand_score[c2] * NX_MAGIC_1000000 + cand_raw[c2]), c2); c2 = c2 + 1 } 406 let ok: *i64 = sys_mmap(8 * k) as *i64 407 let oi: *i64 = sys_mmap(8 * k) as *i64 408 let nres: i64 = ie_heap_drain(hd, hp, nbox[0], ok, oi) 409 410 let parr: *i64 = e.pays as *i64 411 var i: i64 = 0 412 while i < nres { 413 let c: i64 = oi[i] 414 out_pay[i] = parr[cand_item[c]] 415 out_score[i] = cand_score[c] 416 out_tier[i] = cand_tier[c] 417 out_dist[i] = cand_dist[c] 418 i = i + 1 419 } 420 return nres 421} 422 423// Full reverse-image search: every tier votes. This is the "show me results" call. 424func nx_imgengine_query(e: *nx_imgengine, gray: *u8, rgb: *u8, w: i64, h: i64, topk: i64, 425 out_pay: *i64, out_score: *i64, out_tier: *i64, out_dist: *i64) -> i64 { 426 return nx_imgengine_query_class(e, gray, rgb, w, h, topk, 0, out_pay, out_score, out_tier, out_dist) 427} 428 429// THE IDENTITY QUESTION: "is this exact image (or a modified copy of it) already in the corpus?" 430// Dedup, provenance, copyright. Only IDENTITY-class tiers vote -- a similarity tier's whole purpose 431// is returning pictures that are NOT the query, so its opinion here would be a confident lie. 432// Returns the payload, or -1 when no identity tier is within its own threshold: an honest ABSENT, 433// never the nearest thing anyway. 434// out_reason: 0 = matched, 1 = nothing within threshold, 2 = query carries too little visual 435// information for ANY identity tier to be believed (flat / smooth-gradient / near-blank image). 436// Reason 2 exists because it was found on real data: structureless images all hash identically and 437// would otherwise be reported as confident matches for one another. 438func nx_imgengine_best_match_r(e: *nx_imgengine, gray: *u8, rgb: *u8, w: i64, h: i64, 439 out_tier: *i64, out_dist: *i64, out_reason: *i64) -> i64 { 440 out_tier[0] = 0 - 1 441 out_dist[0] = 0 - 1 442 443 // INFORMATIVENESS GATE, before any ranking: if no identity tier considers the query's own 444 // descriptor meaningful, there is nothing to be confident about and we say so. 445 var informative: i64 = 0 446 var ti: i64 = 0 447 while ti < e.ntiers { 448 let t: *nx_imgtier = nx_imgengine_tier(e, ti) 449 if nx_imgtier_klass(t) == NX_IT_CLASS_IDENTITY { 450 let qd: *i64 = sys_mmap(8 * nx_imgtier_qry_dim(t)) as *i64 451 nx_imgtier_describe_query(t, gray, rgb, w, h, qd) 452 if nx_imgtier_informative(t, qd) == 1 { informative = 1 } 453 } 454 ti = ti + 1 455 } 456 if informative == 0 { out_reason[0] = 2; return 0 - 1 } 457 458 let p: *i64 = sys_mmap(8) as *i64 459 let s: *i64 = sys_mmap(8) as *i64 460 let t2: *i64 = sys_mmap(8) as *i64 461 let d: *i64 = sys_mmap(8) as *i64 462 let n: i64 = nx_imgengine_query_class(e, gray, rgb, w, h, 1, NX_IT_CLASS_IDENTITY, p, s, t2, d) 463 if n <= 0 { out_reason[0] = 1; return 0 - 1 } 464 let tier: *nx_imgtier = nx_imgengine_tier(e, t2[0]) 465 out_tier[0] = t2[0] 466 out_dist[0] = d[0] 467 if d[0] <= nx_imgtier_threshold(tier) { out_reason[0] = 0; return p[0] } 468 out_reason[0] = 1 469 return 0 - 1 470} 471 472func nx_imgengine_best_match(e: *nx_imgengine, gray: *u8, rgb: *u8, w: i64, h: i64, 473 out_tier: *i64, out_dist: *i64) -> i64 { 474 let r: *i64 = sys_mmap(8) as *i64 475 return nx_imgengine_best_match_r(e, gray, rgb, w, h, out_tier, out_dist, r) 476}