solid op• Data kinds: image2d × image2d × flow2d → image2d
• Call: import fullseye as fs; fs.ledger.correlation_quality(ref: 'Any', cur: 'Any', flow: 'Any', info: 'Optional[dict]' = None, subset: 'int' = 31) -> 'np.ndarray' (to call the implementation directly, import dic; dic.correlation_quality(ref: 'Any', cur: 'Any', flow: 'Any', info: 'Optional[dict]' = None, subset: 'int' = 31) -> 'np.ndarray'; from the registry, opspiv.get("correlation_quality"))
A ZNCC map that returns, point by point, how well a given displacement field matches.
Arguments
ref reference image.
cur image after deformation. Same shape as ref.
flow flow2d (2, h, w) of pivops, components (dy, dx) [px].
Without info, an all-pixel field of (2, H, W).
info the dict returned by piv_cross_correlate. If given, the window-grid flow
is spread bilinearly to pixels before measuring.
subset side of the square subset for correlation [px]. Odd.
The return value is a float array of the same shape as the input image. Values are ZNCC in [-1, 1]; points that could not be measured are NaN (borders where the subset sticks out of the image, points whose deformed sample points leave the image, subsets without variance).
★ This is not a correlator —— it is a scorer for an existing displacement field
Neither piv_cross_correlate nor optical_flow_lk nor demons_register
tells you whether the displacement it produced is correct. This only pulls cur back to the reference side with flow
(bilinear) and measures the ZNCC against ref in a subset box.
The point is that the output of any estimator can be scored through the same interface: quality alone is added
without adding 1 more correlator.
Both mean and standard deviation are discounted, so it is invariant to gain and offset.
Even for a pair with changed exposure, it only looks at "whether they match".
★★ There are cases where the existing info["peak_ratio"] is not enough (measured)
peak_ratio (1st peak / 2nd peak) is the standard SN ratio of PIV and
catches failures of the kind where competing peaks rise on the correlation surface. What it does not catch
are failures of the kind "that place became something else". Measured on a pair with u=0.37 px where only 60x60 pixels
were replaced by an unrelated pattern:
metric replaced area healthy area distinction
peak_ratio 1.207 1.304 7.5 % apart (overlapping)
correlation_quality 0.1101 0.9993 9 times apart
How the RMS displacement error changes when cutting by quality:
gate kept error RMS [px]
none 100.0 % 1.8110
zncc >= 0.8 84.9 % 0.0031 ← 584 times better
peak_ratio >= 1.2 81.8 % 1.2183 ← 1.5 times
peak_ratio >= 1.3 41.6 % 1.4471 ← discards 6 tenths and gets worse
Looking at both is correct. peak_ratio sees competing peaks, ZNCC sees
decorrelation. Either one alone leaves a hole.
★★ Pitfall —— high quality does not mean high accuracy
ZNCC barely reacts to subpixel error. Measured by scoring fields with deliberately injected error on a pair with true displacement 0.37 px
(speckle diameter 3.8 px):
displacement error [px] 0.00 0.05 0.10 0.25 0.50 1.00 2.00
zncc median 0.99945 0.99920 0.99845 0.99330 0.97615 0.90617 0.67655
Wrong by 0.05 px and still 0.9992 —— the difference from the perfect match 0.99945 is
only 2.5e-4. What this metric can measure is "offsets large compared with the speckle
size, missing data, replacement by something else", not 0.01 px precision.
When all points degrade equally under uniform noise (σ=0.15), the gate keeps only
21.8 % and the error RMS improves merely 1.34 times, 0.1736 → 0.1292 ——
it only helps when the failure is local.
★ The pull-back uses the field itself, not a per-subset translation
Ordinary DIC shifts the subset rigidly and correlates, but here cur is warped with the per-pixel
flow before correlating in a box. If flow is smooth
the two agree; if not, this one is more correct (since it looks at the residual including
deformation within the subset). The cost is only O(N) (0.003 seconds at 256²).
fail-closed
• ref / cur not 2-D, different shapes, containing NaN / Inf.
• flow not (2, h, w).
• without info, flow of a different shape from the image (rejected with instructions for the fix).
• info lacking rows / cols, grid and flow shapes disagreeing, grid smaller than 2 along either axis.
• subset even / less than 3 / larger than the image.
• piv_displacement family guide
• 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.
• piv_field_analysis_tour — py -3.11 examples/piv_field_analysis_tour.py
image2d as input)piv_cross_correlate · piv_multipass · piv_deform_pass · strain_from_displacement · speckle_quality
solid)strain_from_displacement · speckle_quality
*Provenance: dic.py — PIV 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.