ncd — IMGMETRICS compression op

• Data kinds: image2d × image2d → scalar

• Call: import fullseye as fs; fs.ledger.ncd(a, b, compressor='lzma', levels=None, data_range=None, symmetric=True) (to call the implementation directly, import imgmetrics; imgmetrics.ncd(a, b, compressor='lzma', levels=None, data_range=None, symmetric=True); from the registry, opsimgmetrics.get("ncd"))

Usage

Normalised compression distance (Li, Chen, Li, Ma & Vitányi, IEEE TIT 50(12), 2004).

NCD(x,y) = (C(xy) - min(C(x),C(y))) / max(C(x),C(y)). It approaches 0 for identical inputs and 1 for unrelated ones -- but a real compressor is not the ideal Kolmogorov complexity, so even identical inputs do not give exactly 0 (there is an offset for the header). The measured value of that offset is kept in the tests.

Raw float arrays are not accepted. A compressor looks for repeated byte sequences, so values that differ only very slightly replace the float mantissas wholesale, and even 2 similar images lose their common byte sequences. Measured (2026-09-02, a linspace gradient and the same plus 0.02, PSNR 34.0 dB = very similar):

• As float64: sharing of 8-byte words 0.02 % → NCD 1.0959 (a value that reads as "completely unrelated", and above 1 at that)

• Quantised to 256 levels: NCD 0.1290 (correctly "very similar")

• 2 truly unrelated images: NCD 0.9981

In other words, measuring as float gives the opposite conclusion without exception. Therefore float requires an explicit levels (levels=256 quantises data_range into 256 levels before measuring). Integer dtypes can be measured as is.

symmetric=True (default) measures both directions and averages. A compressor builds its dictionary from front to back, so C(xy) != C(yx), and a naive implementation calls itself a "distance" while its value changes with argument order (measured: vertical vs horizontal stripes 0.571429 vs 0.595238, 4 % relative). Between uniform random inputs the difference is 0.000e+00, so as long as you test with random data you cannot notice -- a type of defect that only breaks on real data with structure.

Detailed usage guide

• image_difference_metrics family guide

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• image_quality_metrics — py -3.11 examples/image_quality_metrics.py

Ops the type connects to (they accept scalar as input)

luma_limited_u8 · rank_data · tid2013_index

Same category (compression)

compressed_size


*Provenance: imgmetrics.py — IMGMETRICS operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.