correlation_quality — PIV 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"))

Usage

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.

Detailed usage guide

• piv_displacement 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)

• piv_field_analysis_tour — py -3.11 examples/piv_field_analysis_tour.py

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

piv_cross_correlate · piv_multipass · piv_deform_pass · strain_from_displacement · speckle_quality

Same category (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.