solid op• Data kinds: image2d × image2d → image2d
• Call: import fullseye as fs; fs.ledger.strain_from_displacement(u: 'Any', v: 'Any', window: 'int', method: 'str', spacing: 'float' = 1.0) -> 'tuple[np.ndarray, np.ndarray, np.ndarray]' (to call the implementation directly, import dic; dic.strain_from_displacement(u: 'Any', v: 'Any', window: 'int', method: 'str', spacing: 'float' = 1.0) -> 'tuple[np.ndarray, np.ndarray, np.ndarray]'; from the registry, opspiv.get("strain_from_displacement"))
• Return value through the ledger: fullseye.ledger.strain_from_displacement(...) returns only the declared out type image2d (the underlying function also returns auxiliary values). When you need what was dropped, use fullseye.ledger.strain_from_displacement.raw(...) or call dic.strain_from_displacement directly.
Computes the strain field from a displacement field. Returns (exx, eyy, exy).
Arguments (window and method have no default; the reason is below)
u displacement field in the x direction (columns) [px]. 2-D.
v displacement field in the y direction (rows) [px]. Same shape as u.
window side of the local least-squares window. The unit is grid nodes (not pixels).
Odd.
method "infinitesimal" or "green". Anything else raises.
spacing node spacing [px]. 1.0 (default) for a dense all-pixel field.
info["step"] for the grid of piv_cross_correlate.
★ When passing from pivops —— getting the component order wrong breaks silently flow2d is in (dy, dx) order. Correct usage::
flow, info = piv_cross_correlate(a, b, window=32, overlap=0.75)
exx, eyy, exy = strain_from_displacement(
flow[1], flow[0], 9, "green", spacing=info["step"])
Passing flow[0] as u swaps x and y, and a plausible different answer comes back without an exception. Only a 3-D array passed as-is can be rejected with instructions for the fix, so that case is checked.
Definition ux = ∂u/∂x etc. are obtained by a plane fit over window x window, and
• "infinitesimal" (infinitesimal strain, engineering strain) exx = ux, eyy = vy, exy = ½(uy + vx)
• "green" (Green-Lagrange) exx = ux + ½(ux² + vx²), eyy = vy + ½(uy² + vy²), exy = ½(uy + vx + ux·uy + vx·vy)
★★ Why method has no default —— the lie created by rigid-body rotation Suppose the specimen has merely rotated by θ and the material has not stretched by even 1 micron. The true strain is 0. The exact rigid-body-rotation displacement field u = (cosθ-1)x - sinθ·y, v = sinθ·x + (cosθ-1)y fed in directly, without going through images, measured (unit µε = 1e-6):
θ [deg] cosθ-1 infinitesimal piv_velocity_gradient green
0.5 -38.1 -38.1 -38.1 5.3e-14
1.0 -152.3 -152.3 -152.3 9.3e-14
2.0 -609.2 -609.2 -609.2 2.0e-13
5.0 -3805.3 -3805.3 -3805.3 6.5e-13
-609 µε at 2 degrees: 3 tenths of the yield strain of steel (about 2000 µε). For Green-Lagrange, exx = (cosθ-1) + ½((cosθ-1)² + sin²θ) = 0 is algebraically exact, so it drops to 1e-13. The existing piv_velocity_gradient returns the same value as infinitesimal strain (= carries the same lie), and piv_strain_rate returns +1218 µε at 2 degrees (2|cosθ-1|) —— a quantity that would be 0 for the linearised fluid rotation u=-ωy, v=ωx, but is not 0 for finite rotation.
Conversely, in material-test reports, standards and comparisons with strain gauges, infinitesimal strain is the standard, and silently returning Green-Lagrange makes the numbers disagree. Which one is right flips with the situation. That is why you must choose.
★ But "use green and you are safe" is not true —— estimation error passes straight through Green's correction term ½(ux²+vx²) is built from the estimated gradient, so if the gradient estimate is bad, so is the correction. Measured (analytic speckle 256², rigid-body rotation, mean ± scatter inside MARGIN 40, unit µε):
θ [deg] cosθ-1 | piv+LS w=9 infin. green | lk+LS w=31 infin. green
2.0 -609.2 | -42.9 ± 428 564.7 ± 429 | -698.8 ± 2122 -84.4 ± 2132
5.0 -3805.3 | 364.9 ± 9532 3843.4 ± 9638 | -1372.9 ±22909 2759.6 ±25685
lk at 2 degrees is textbook (-699 ≒ the -609 lie → reduced to -84 by green). But piv at 2 degrees returns only -42.9 even for infinitesimal strain —— rotating subsets blunt the correlation so that the gradient estimate itself fluctuates by ±500 µε, and adding the +609 µε Green correction on top turns it into +565 µε instead. At 5 degrees and above both estimators scatter beyond 1e4 µε, and the mean is meaningless. The argument about definitions only matters once the gradient is measured to that precision.
★ window is the spatial resolution itself Measured, reading a uniform strain back from the same piv displacement field (µε):
truth piv_velocity_gradient w=3 w=5 w=9
100 100.4 ± 21.8 100.4 ± 16.0 100.3 ± 6.4 100.3 ± 1.7
500 501.8 ± 107.2 502.0 ± 78.9 501.4 ± 31.3 501.2 ± 8.6
2000 1996.3 ± 342.0 1996.6 ± 259.5 1994.8 ± 99.3 1995.6 ± 25.9
20000 19768.8 ± 2909.9 19768.9 ± 2210.5 19742.7 ± 790.9 19754.1 ± 209.6
The means are all the same. Only the scatter shrinks with w, by up to 13.9 times. In exchange, w=9 regards 9 nodes (72 px if step=8) as flat, so in strain fields with curvature, such as at a notch tip, it under-reads the peak. Least squares over a symmetric window returns 1st-order strain fields exactly, but blunts curved ones. With a default, numbers would come out on top of a trade-off you do not remember choosing.
★ Handling of NaN —— not filled with 0 If u or v has a NaN, the window x window fit containing that point is undefined. All outputs of windows containing NaN become NaN (1 mask combining the missing values of both u/v is applied to the 3 components). Treating NaN as 0 makes unmeasured points act on the fit as "points with displacement 0" and creates fake strain gradients around them that look real.
fail-closed
• u / v not 2-D, different shapes, smaller than 4x4.
• a whole flow2d of (2, h, w) passed as u (rejected with instructions for the fix).
• window even / less than 3 / larger than the field.
• spacing non-positive / non-finite.
• method other than "infinitesimal" / "green".
• a call omitting window and method gives TypeError (there is no default, so it simply fails in Python's argument mechanism).
• 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.
• poc_dic_strain — py -3.11 examples/poc_dic_strain.py
image2d as input)piv_cross_correlate · piv_multipass · piv_deform_pass · correlation_quality · speckle_quality
solid)correlation_quality · 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.