Nishi Reverse Image — sovereign visual search
Find where an image (or a modified copy of it) appears — TinEye / Google-Lens class matching, built bits-up in NishiLang. Pure integer, zero-float, no cloud API. Every number below is measured by an in-repo ruler, not asserted.
A reverse-image engine lives or dies on one question: which real-world edits to an image does it still recognise? So the engine ships with its own adversary — nx_imgbench applies 16 named modifications (rescale, crop, letterbox, mirror, rotate, brightness, contrast, watermark, recompress, sensor noise) to a deterministic corpus and measures recall@1 for every one. That measurement is the spec; a class that scores below par is a build item, not a footnote.
Measured robustness — recall@1 per modification class
Multi-tier engine on the same corpus. Higher = more of the modified copies still rank #1. ≥900‰ survived below par
The 16 single-edit classes all survive; the fused engine scores 985‰ overall vs 597‰ for a single perceptual hash. *crop+rescale is a harder combined stress test — occlusion AND a scale change at once — added to push the frontier. A similarity-consensus RANSAC (translation + scale, still pure integer) lifts it from a translation-only 500 to 843‰. Recall@1 is measured against 256 distractors and stated as an upper bound on open-web performance.
How it works — four tiers, fused
No single descriptor covers the whole modification space, so the engine fuses complementary tiers by reciprocal-rank fusion with abstention (a tier only votes where it is competent, so a blind-but-confident tier can't out-vote the right one). Adding a tier is a new subclass, never an engine rewrite.
| tier | method | answers | beats |
|---|---|---|---|
| copy‑dhash | 64‑bit perceptual hash (dHash), Hamming | identity | rescale, brightness, contrast, noise, watermark, recompress |
| orient‑d4 | dihedral‑group canonical hash | identity | mirror, 90°/180° rotation — zero extra storage |
| local‑orb | FAST corners + BRIEF + RANSAC geometry | identity | cropping, letterboxing — occlusion-robust |
| similar‑ehd | edge‑orientation layout, L1 | looks‑alike | “same composition” when it isn't a byte copy |
Scale: the hash tiers generate candidates through a BK‑tree metric index — exact within threshold, ~6× fewer comparisons than a linear scan on clustered corpora. Descriptors are quantised to integers at ingest and served with fast integer scans.
Honesty — what it does NOT do yet
Crop + rescale (843‰)
The combined occlusion + scale case, lifted from 500 to 843 by the similarity-consensus RANSAC. Clearing 900 wants a full ORB scale pyramid — and a naive octave pyramid was measured to regress the same-scale classes, so it is honestly not shipped.
Semantic “looks like”
The Google-Lens axis (what a picture is of) needs a quantised learned embedding tier. Not built yet — and the page says so rather than implying it.
WebP / AVIF
Those formats have no sovereign decoder yet, so that slice of the web is currently un-indexable — invisible, not mis-ranked.
Query it (agents & workflows)
The engine is a first-class MCP tool — strict JSON in, strict JSON out, self-describing including its own measured limits. It refuses honestly: a structureless (blank / gradient) image returns present:false rather than a confident collision.
nx_imgsearch status # live tier roster + measured robustness + named gaps nx_imgsearch index <manifest> <out> # build a fingerprint index (cid → image) nx_imgsearch walkdir <dir> <out> [max] # index a whole folder (async), poll with walkstatus nx_imgsearch query <index> <image> [k] # → ranked matches, each with tier, distance, confidence
Proven live on real photorealistic images: a held-out photo returns its own record at Hamming distance 0 via the local-feature tier, while a genuinely-different image is honestly reported absent.