code wiki / (root) / nx_imgorient.nx

nx_imgorient.nx

buildroot/runtime/nx_imgorient.nx

7328 B169 linesdepth 6pulls 8 transitivereach 5 importersview sourcekind library
docsdependenciesstructsconstsfunctions

about

nx_imgorient.nx -- DIHEDRAL (D4) ORIENTATION-INVARIANT perceptual matching. MEASURED MOTIVATION (nx_imgbench, 2026-07-23): the live reverse-image client scores mirror-horizontal recall@1 = 0 permille rotate-90 recall@1 = 31 permille rotate-180 recall@1 = 31 permille and det@10 = ZERO for all three -- i.e. a horizontally mirrored copy of an indexed image is, to the production engine, a completely different picture. Mirroring is not exotic: it is what every meme repost, every "flipped to dodge copyright detection" upload, and every front-camera selfie does. THE FIX, and why it is nearly free: the eight rigid symmetries of the square (identity, three rotations, and those four composed with a mirror) form the dihedral group D4. Because D4 is a GROUP, the orbit {g.A : g in D4} of an image A is IDENTICAL to the orbit of any dihedral variant of A -- applying h to a rotated copy just re-labels which element of the same eight you land on. Therefore: * the MULTISET of the eight orientation fingerprints is a dihedral INVARIANT of the image, and * min-of-eight is a CANONICAL FORM: canonical(A) == canonical(g.A) for every g, exactly, always. No training, no floats, no index change, no extra storage -- pure integer algebra over the existing nx_phash dHash. Eight extra 9x8 downscales per image is nothing. TWO SERVING MODES, deliberately both offered (they trade different things): canonical -- store min-of-8, one probe per query. Cheapest; loses WHICH orientation matched and very slightly raises collision odds (eight images now share one bucket key). query-side -- store the plain dHash unchanged, compute the query's eight and probe eight times. Zero information lost, zero index migration, reports the matching orientation. Costs eight index probes, which a sublinear index (nx_phash_index BK-tree) absorbs. INDEPENDENCE NOTE (rigor, deliberate): the geometric transforms here are implemented from scratch rather than imported from nx_imgxform. nx_imgxform is the ADVERSARY that the ruler attacks us with; this file is the DEFENCE. Sharing one implementation would let a single indexing bug cancel itself out and score as a pass -- the defence must be able to disagree with the attack. genealogy_id: dihedral_group_d4 (classical) over krawetz_2011 dHash license_tier: ORIGINAL

dependencies 1 imports · 1 importers

nx_phash.nx nx_imgorient.nx nx_imgsearch_tier.nx

imports: nx_phash.nx

imported by: nx_imgsearch_tier.nx

structs

none

consts

36const OR_COUNT: i64 = 8 // |D4|
38const OR_IDENT: i64 = 0
39const OR_ROT90: i64 = 1
40const OR_ROT180: i64 = 2
41const OR_ROT270: i64 = 3
42const OR_FLIPH: i64 = 4
43const OR_TRANSP: i64 = 5
44const OR_FLIPV: i64 = 6
45const OR_ANTIT: i64 = 7

functions

47func nx_orient_count() -> i64 { return OR_COUNT }
called by 1: main
49func nx_orient_name(k: i64) -> *u8
62func or_swaps_dims(k: i64) -> i64
called by 1: nx_orient_dims
71func nx_orient_dims(k: i64, w: i64, h: i64, outwh: *i64) -> i64
called by 1: nx_orient_apply calls 1: or_swaps_dims
79func nx_orient_apply(k: i64, gray: *u8, w: i64, h: i64, outwh: *i64) -> *u8
called by 2: nx_orient_hashesmain calls 1: nx_orient_dims
107func nx_orient_hashes(gray: *u8, w: i64, h: i64, out8: *i64) -> i64
122func or_ult(a: i64, b: i64) -> i64
132func nx_orient_canonical(gray: *u8, w: i64, h: i64) -> i64
called by 1: main calls 2: nx_orient_hashesor_ult
145func nx_orient_canonical_of(h8: *i64) -> i64
calls 1: or_ult
158func nx_orient_best_hamming(q8: *i64, target: i64, out_k: *i64) -> i64