skills/openpiv/references/advanced_algorithms.md
Everything here is checked against openpiv 0.25.4. Confirm with inspect.signature() against
other releases — names and defaults have moved between versions.
pyprocess.extended_search_area_piv(..., correlation_method=...):
| Value | Behaviour |
|---|---|
"circular" (default) | FFT correlation with no zero-padding. Fastest and lowest memory. Wrap-around means a displacement past half the window aliases back as a small one in the opposite direction. |
"linear" | FFT correlation zero-padded to 2*window_size. No wrap-around, so large displacements survive, at roughly 2–4× the cost. |
Only those two exist in 0.25.4. "direct" appears in the docstrings but the branch is missing —
it prints correlation method direct is not implemented and then raises
UnboundLocalError: cannot access local variable 'corr'. Do not offer it as an option.
Use "linear" whenever search_area_size > window_size. "circular" accepts an extended search
area without complaining but keeps relying on wrap-around, and on OpenPIV's own test1 pair at
window_size=32, search_area_size=38 it produced a peak |u| of 255 px/s against 87 px/s for
"linear" — the difference is aliased vectors, not physics.
normalized_correlation=True normalizes intensities per window before correlating, making peak
heights comparable across windows of differing brightness — useful under uneven illumination. It also
shifts the s2n scale, so re-tune the threshold after switching it on.
use_vectorized=True swaps the per-window loop for the batched
vectorized_correlation_to_displacements path. Same results, faster on large fields, higher peak
memory since all correlation maps exist at once.
subpixel_method selects how the integer correlation peak is refined:
| Value | Notes |
|---|---|
"gaussian" (default) | Three-point Gaussian fit per axis. Standard choice; biased toward integer values ("peak locking") when particle images are under 2 px. |
"parabolic" | Three-point parabolic fit. Cheaper, slightly less accurate for Gaussian particle images. |
"centroid" | Intensity-weighted centroid. More robust for wide or saturated peaks. |
Peak locking is a particle-imaging problem, not a fitting problem: aim for 2–3 px particle image diameter rather than switching estimators.
pyprocess.sig2noise_ratio(correlation, sig2noise_method="peak2peak", width=2):
"peak2mean" — first peak divided by the mean of the correlation map. This is the default of both
extended_search_area_piv and PIVSettings. Values run higher and depend on map size."peak2peak" — first peak divided by the second-highest peak, excluding a width-pixel
neighbourhood around the first. The classic PIV detectability ratio; usable thresholds are
~1.05–1.3.The two scales are not interchangeable. A threshold copied from one to the other silently rejects everything or nothing.
openpiv.windef)windef.simple_multipass(frame_a, frame_b, settings) runs the full loop:
windef.first_pass — coarse correlation on windowsizes[0].validation.typical_validation — applies every enabled check in settings at once.filters.replace_outliers — fills flagged vectors.windef.multipass_img_deform for iterations 1 .. num_iterations-1 — deforms the interrogation
windows using the previous pass as a predictor, then re-correlates on the next smaller window.transform_coordinates applied.It returns (x, y, u, v, flags) in pixels per frame. settings.dt and
settings.scaling_factor exist on the dataclass but the windef chain applies neither —
first_pass calls extended_search_area_piv without passing dt. Convert afterwards:
x, y, u, v = scaling.uniform(x, y, u, v, scaling_factor=96.52)
u, v = u / dt, v / dt # scaling.uniform does not divide by dt
The upside of working in px/frame is that the validation defaults (min_max_u_disp=(-30, 30),
median_threshold=3) are stated in px/frame and therefore mean what the PIV literature says they
mean. The same numbers applied to extended_search_area_piv output, which has been divided by
dt, would reject the whole field.
deformation_method="symmetric" (the default) deforms both frames toward the midpoint, which halves
the interpolation bias of deforming only the second frame. interpolation_order (default 3) is the
spline order used for that deformation.
Window deformation is what makes multi-pass worth the cost: it handles velocity gradients within a window, which a fixed-window single pass cannot.
PIVSettings referencewindef.PIVSettings() is a dataclass; set attributes on an instance.
Input and region
| Field | Default | Meaning |
|---|---|---|
filepath_images, save_path, frame_pattern_a, frame_pattern_b | OpenPIV's bundled data/test1 | Batch-mode paths; irrelevant when calling simple_multipass with arrays |
roi | "full" | "full" or (y1, y2, x1, x2) crop |
invert | False | Invert intensities, for dark particles on a bright background |
Masking
| Field | Default | Meaning |
|---|---|---|
dynamic_masking_method | None | None, "edges", or "intensity" |
dynamic_masking_threshold | 0.005 | Edge-strength threshold for "edges" |
dynamic_masking_filter_size | 7 | Gaussian/median filter size in px |
static_mask | None | Boolean array marking permanently excluded pixels |
Correlation
| Field | Default |
|---|---|
correlation_method | "circular" (or "linear") |
normalized_correlation | False |
windowsizes | (64, 32, 16) |
overlap | (32, 16, 8) |
num_iterations | 3 |
subpixel_method | "gaussian" |
use_vectorized | False |
deformation_method | "symmetric" |
interpolation_order | 3 |
windowsizes and overlap must be at least num_iterations long — each pass reads its own entry.
Scaling
| Field | Default | Meaning |
|---|---|---|
dt | 1.0 | Seconds between frames — ignored by the windef chain |
scaling_factor | 1.0 | Pixels per physical unit — ignored by the windef chain |
Validation — all consumed by validation.typical_validation
| Field | Default | Meaning |
|---|---|---|
sig2noise_method | "peak2mean" | See above |
sig2noise_mask | 2 | width around the first peak for "peak2peak" |
sig2noise_threshold | 1.0 | Reject below this |
sig2noise_validate | True | Enable the s2n check |
validation_first_pass | True | Also validate the coarse pass |
min_max_u_disp, min_max_v_disp | (-30, 30) | Global range in px/frame |
std_threshold | 10 | Reject beyond N standard deviations |
median_threshold | 3 | Local-median residual threshold |
median_size | 1 | Neighbourhood half-width for the median test |
median_normalized | False | Normalize the median residual by local fluctuation |
Replacement and smoothing
| Field | Default | Meaning |
|---|---|---|
replace_vectors | True | Run replace_outliers after validation |
filter_method | "localmean" | "localmean", "disk", or "distance" |
max_filter_iteration | 4 | Inpainting iterations |
filter_kernel_size | 2 | Inpainting kernel size |
smoothn | False | Apply smoothn between passes |
smoothn_p | 0.05 | Smoothing strength when enabled |
Smoothing between passes stabilizes the predictor for the next pass. It also propagates smoothing into the final result, so report it as part of the processing chain.
Output
| Field | Default |
|---|---|
save_plot, show_plot, show_all_plots | False |
scale_plot | 100 |
fmt | "%.4e" |
openpiv.pyprocess3D)from openpiv import pyprocess3D
u, v, w, s2n = pyprocess3D.extended_search_area_piv3D(
vol_a, vol_b,
window_size=(32, 32, 32),
overlap=(16, 16, 16),
dt=(1.0, 1.0, 1.0),
search_area_size=(38, 38, 38),
correlation_method="fft", # note: "fft" here, not the 2D "circular"/"linear"
subpixel_method="gaussian",
sig2noise_method="peak2peak",
)
# Note the extra window_size argument -- this signature differs from pyprocess.get_coordinates.
x, y, z = pyprocess3D.get_coordinates(
vol_a.shape, search_area_size=(38, 38, 38), window_size=(32, 32, 32), overlap=(16, 16, 16)
)
Inputs are 3D intensity volumes — this module correlates reconstructed volumes; it does not perform
the tomographic reconstruction itself. dt is a per-axis tuple. Memory scales with the cube of
window size, so 32³ windows on a large volume are already demanding.
openpiv.phase_separation)For two-phase flows where large particles (droplets, bubbles) must be separated from tracers before correlation:
from openpiv import phase_separation
big, small = phase_separation.khalitov_longmire(
image,
big_particles_criteria={"min_size": 20, "min_brightness": 30},
small_particles_criteria={"max_size": 20, "min_brightness": 5},
blur_kernel_size=1,
I_sat=230,
)
Criteria dicts accept min_size, max_size, min_brightness, and max_brightness. min_size is
mandatory for the big-particle dict and max_size for the small-particle dict; unrecognized keys are
ignored silently, so check spelling.
Also available: median_filter_method(image, kernel_size) (Kiger & Pan) and
opening_method(image, kernel_size, iterations=1, thresh_factor=1.1) for simpler size-based
separation. Run PIV separately on each returned phase — tracer statistics computed on an unseparated
image are contaminated by the dispersed phase.
| Situation | Approach |
|---|---|
| Small displacements, uniform flow | extended_search_area_piv, correlation_method="circular" |
| Displacements above ~1/4 window | search_area_size > window_size with correlation_method="linear" |
| Strong velocity gradients, shear layers | windef.simple_multipass with decreasing windowsizes |
| Uneven illumination | normalized_correlation=True, plus background subtraction |
| Solid bodies, reflections, free surfaces | preprocess.dynamic_masking or a static_mask |
| Two-phase flow | phase_separation first, then PIV per phase |
| Volumetric data | pyprocess3D.extended_search_area_piv3D |