Openpiv
K-DenseCommunityCreated by OpenPIV TeamDistributed by K-Dense
- Skill
- Tool integration
- Science and research
- BSD-3-Clause
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
OpenPIV is a specialized tool for Particle Image Velocimetry, enabling researchers to measure fluid flow by tracking tracer particles across image pairs. The skill provides a comprehensive pipeline for PIV analysis, including preprocessing, cross-correlation, vector validation, outlier replacement, and physical scaling. It supports both single-pass analysis and multi-pass window deformation for complex flows, allowing for the computation of vorticity, strain rates, and turbulence statistics.
What it does
- Extracts 2D velocity fields from experimental PIV image pairs.
- Performs cross-correlation using linear or circular methods with configurable interrogation windows.
- Validates velocity vectors using signal-to-noise ratios, global range, and local median checks.
- Replaces spurious vectors using interpolation methods like local mean or disk filtering.
- Computes derived flow properties including vorticity, strain rate, and turbulence statistics.
- Provides visualization tools for plotting vector fields and velocity components.
When to use it
- When analyzing experimental fluid dynamics or flow visualization data.
- When you need to convert pixel-based particle displacements into physical velocity units.
- When performing multi-pass processing to resolve high-gradient flow regions.
- When you need to audit and clean noisy PIV data using robust validation and filtering techniques.
Advantages
- Supports both simple single-pass and advanced multi-pass window deformation workflows.
- Includes built-in validation and outlier replacement to ensure data integrity.
- Provides flexible scaling and coordinate transformation for physical unit analysis.
- Offers a command-line interface for batch processing and automated analysis pipelines.
Usage examples
- “Run a basic PIV analysis on frame_a.bmp and frame_b.bmp with a 32px window size.”
- The agent executes the CLI runner, processing the images to generate a velocity field, saving the results as vectors.txt and a visualization plot.
- “How do I compute vorticity from my processed PIV data?”
- The agent provides the Python code to use the PIVAnalyzer class or the compute_vorticity function, ensuring correct handling of physical grid spacing.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/k-dense-ai/openpiv/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip openpiv.zip -d .agents/skills
Documentation by OpenPIV Team
OpenPIV
Overview
OpenPIV (Open Particle Image Velocimetry) analyzes fluid flow from PIV image pairs. It covers preprocessing, cross-correlation, vector validation, outlier replacement, smoothing, and scaling to physical units.
Targets openpiv 0.26.1. Synthetic checks ran on Python 3.13, NumPy 2.5.3, SciPy 1.18.1,
scikit-image 0.26.0, and Matplotlib 3.11.2. Rust was not installed; use backend="scipy" for the
tested path. See review evidence.
When to use
Use this skill when working with experimental PIV or flow-visualization image pairs: measuring 2D velocity fields, tuning interrogation-window parameters, validating vectors, or deriving vorticity, strain rate, and turbulence statistics. For simulating flow rather than measuring it, use a CFD skill instead.
Quick Start
Install OpenPIV:
uv pip install openpiv
# Pin it when the analysis needs to be reproducible -- this is the version every
# snippet below was checked against.
uv pip install "openpiv==0.26.1"Run PIV analysis on an image pair:
import numpy as np
from openpiv import tools, pyprocess, validation, filters, scaling
frame_a = tools.imread("image_a.bmp")
frame_b = tools.imread("image_b.bmp")
# Always returns (u, v, s2n); s2n is all NaN if sig2noise_method=None.
u, v, s2n = pyprocess.extended_search_area_piv(
frame_a.astype(np.float32),
frame_b.astype(np.float32),
window_size=32,
overlap=12,
dt=0.02,
search_area_size=38,
correlation_method="linear", # circular also supports extended search
normalized_correlation=True,
backend="scipy",
sig2noise_method="peak2peak",
)
x, y = pyprocess.get_coordinates(
image_size=frame_a.shape,
search_area_size=38,
overlap=12,
center_on_field=False, # matches sliding-window positions
)
# flags is a boolean array: True marks a spurious vector.
flags = validation.sig2noise_val(s2n, threshold=1.05) | ~np.isfinite(s2n)
flags |= ~np.isfinite(u) | ~np.isfinite(v)
u, v = filters.replace_outliers(u, v, flags, method="localmean", max_iter=3, kernel_size=2)
# Scale, then convert to right-handed image-boundary coordinates.
x, y, u, v = scaling.uniform(x, y, u, v, scaling_factor=96.52)
y, v = frame_a.shape[0] / 96.52 - y, -v
tools.save("vectors.txt", x, y, u, v, flags)Or use the bundled CLI, which also checks inputs and preserves masks and processing parameters:
python skills/openpiv/scripts/runner.py \
--image frame_a.bmp --image frame_b.bmp --output_dir results --verboseCore Concepts
PIV Fundamentals
Particle Image Velocimetry is an optical method for measuring fluid velocity by tracking illuminated tracer particles between two images.
Process flow:
Capture an image pair (
frame_a,frame_b) separated by a known timedt.Divide the images into interrogation windows.
Cross-correlate matching windows to find peak displacement.
Validate vectors (signal-to-noise, global range, local median).
Replace spurious vectors with interpolated values.
Scale pixel displacements to physical units.
Interrogation Window Parameters
window_size — correlation window in pixels (typically 16–128). Larger windows give better
correlation but coarser spatial resolution.
overlap — pixels shared between adjacent windows (typically 50–75% of window_size). Higher
overlap raises vector density and cost, but adjacent vectors become correlated rather than
independent.
search_area_size — the window searched in the second frame. Must be ≥ window_size; a few
pixels larger accommodates larger displacements. Both "circular" and "linear" support extended
search. The CLI chooses zero-padded "linear" with normalized_correlation=True for it; this
does not recover arbitrary displacement or eliminate spurious peaks. Grid stride is
search_area_size - overlap, so overlap must be smaller than the search area.
Rules of thumb: keep the largest displacement under about a quarter of window_size, and aim for
5–10 particles per window.
Signal-to-Noise Ratio
s2n measures how distinct the correlation peak is. sig2noise_method controls how it is computed —
"peak2mean" (the function default) or "peak2peak". The two are on different scales, so a
threshold tuned for one is meaningless for the other. Typical peak2peak thresholds are 1.05–1.3.
flags = validation.sig2noise_val(s2n, threshold=1.05)
# flags is bool: True == spurious. `~flags` selects the good vectors.Common Operations
Dynamic Masking
Masking lives in openpiv.preprocess and returns (image, mask). Use floating-point copies
to avoid unsigned subtraction artifacts. Inspect masks on both frames before analysis.
from openpiv import preprocess
# Intensity masking uses Otsu; the threshold argument is for edges.
frame_a_masked, mask_a = preprocess.dynamic_masking(
frame_a.astype(np.float64), method="intensity", filter_size=7, threshold=0.005
)
frame_b_masked, mask_b = preprocess.dynamic_masking(
frame_b.astype(np.float64), method="intensity", filter_size=7, threshold=0.005
)Preserve the union mask_a | mask_b as excluded physical regions. The CLI samples it at
interrogation centers, saves it, and keeps those velocities NaN after replacement. Windows
straddling an object can still be biased: inspect/dilate masks for the experiment. In 0.26.1
method="edges" indexes with uint8 instead of bool, potentially corrupting the image or failing.
The CLI refuses it; use intensity masking or a verified boolean mask in a custom workflow.
Multi-Pass Processing
Multi-pass (window deformation) lives in openpiv.windef, driven by a PIVSettings dataclass.
pyprocess has no multi-pass entry point.
import numpy as np
from openpiv import scaling, windef
settings = windef.PIVSettings(
windowsizes=(64, 32, 16), overlap=(32, 16, 8), num_iterations=3,
backend="scipy", sig2noise_method="peak2peak", sig2noise_threshold=1.05,
)
x, y, u, v, flags = windef.simple_multipass(
frame_a.astype(np.float32), frame_b.astype(np.float32), settings
)
# Output is in PIXELS PER FRAME -- convert yourself. scaling.uniform only divides
# by scaling_factor, so apply dt separately.
dt = 0.02
x, y, u, v = scaling.uniform(x, y, u, v, scaling_factor=96.52)
u, v = u / dt, v / dtsimple_multipass validates, replaces outliers, fills remaining NaNs with zeros, and transforms
coordinates. Keep its flags: repaired/zero-filled output is not an independent measurement.
Always pass settings: its no-settings path has two window sizes but three iterations in 0.26.1.
Units trap: simple_multipass and its alias multigrid_windef ignore settings.dt and
settings.scaling_factor; convert their arrays as above. Batch windef.piv(settings) applies
both before saving — do not scale its files again. The simple wrapper does not honor all batch
preprocessing/output switches.
For control over individual passes, windef.first_pass and windef.multipass_img_deform are the
lower-level building blocks.
Validation and Post-Processing
Validation Methods
Every validator returns a boolean array where True marks a spurious vector. Also reject
nonfinite u, v, and s2n: sig2noise_val alone does not flag NaN.
# Signal-to-noise
flags = validation.sig2noise_val(s2n, threshold=1.05)
# Global range -- takes (min, max) TUPLES, positionally or as u_thresholds/v_thresholds.
flags = validation.global_val(u, v, (-300, 300), (-300, 300))
# Local median -- u_threshold and v_threshold are REQUIRED; size is the neighbourhood half-width.
flags = validation.local_median_val(u, v, u_threshold=30.0, v_threshold=30.0, size=1)
# Combine with boolean OR (not np.maximum -- these are bool arrays).
flags = (
validation.sig2noise_val(s2n, threshold=1.05)
| validation.global_val(u, v, (-300, 300), (-300, 300))
| validation.local_median_val(u, v, u_threshold=30.0, v_threshold=30.0)
)Set these thresholds in the units of u and v, not in pixels per frame.
extended_search_area_piv divides by dt, so with dt=0.02 a 3 px/frame displacement arrives as
150 px/s. The thresholds above suit that case; the (-30, 30) figure that PIV literature and
PIVSettings.min_max_u_disp use is a px/frame limit, and applying it to px/s output rejects the
entire field. Correlate with dt=1 to validate displacements before conversion, or adjust
thresholds to the actual velocity units (including both time and calibration factors).
Outlier Replacement
u, v = filters.replace_outliers(
u, v, flags, method="localmean", max_iter=3, tol=1e-3, kernel_size=2
)method accepts "localmean", "disk", or "distance"; an invalid name raises ValueError
when filling missing cells. Use kernel_size>=2 for "distance": size 1 truncates all neighbor
weights to zero in 0.26.1 and leaves holes. Replacement fills the flagged
positions with interpolated values — if you then overwrite them with NaN, the replacement was
wasted. Choose one or the other:
# Keep flagged vectors out of the analysis entirely, instead of interpolating them.
u = np.where(flags, np.nan, u)
v = np.where(flags, np.nan, v)Smoothing
Smoothing is openpiv.smoothn.smoothn; there is no openpiv.smooth module. It returns a tuple
whose first element is the smoothed field. NaN/Inf are missing observations; supply a copy because
it can modify input. Zero-filling holes first incorrectly treats them as measured zeros.
from openpiv.smoothn import smoothn
u_smooth, *_ = smoothn(np.asarray(u, dtype=float).copy(), s=0.5) # larger == smoother
v_smooth, *_ = smoothn(np.asarray(v, dtype=float).copy(), s=0.5)
u_smooth = np.asarray(u_smooth)Visualization
Vector Field Plotting
display_vector_field reads a saved vectors file and calls plt.show() internally, so select a
non-interactive backend for batch runs.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from openpiv import tools
fig, ax = plt.subplots(figsize=(8, 8))
tools.display_vector_field(
"vectors.txt",
ax=ax,
scaling_factor=96.52, # same factor used in scaling.uniform, to map back onto the image
scale=50,
width=0.0035,
on_img=False,
)
image = tools.imread("frame_a.bmp")
height, width = image.shape
ax.imshow(image, cmap="gray", origin="upper", zorder=-1,
extent=(0, width / 96.52, 0, height / 96.52))
fig.savefig("vector_field.png", dpi=150, bbox_inches="tight")
plt.close(fig)Set image extents explicitly: the helper's on_img=True infers them from the last vector and
can stretch overlays when windows leave unused margins. The CLI plots with the actual image bounds.
Custom Visualization
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
mag = np.sqrt(u**2 + v**2)
for ax, field, title, cmap in [
(axes[0], mag, "Velocity Magnitude", "viridis"),
(axes[1], u, "U Velocity", "RdBu_r"),
(axes[2], v, "V Velocity", "RdBu_r"),
]:
im = ax.imshow(field, cmap=cmap)
ax.set_title(title)
plt.colorbar(im, ax=ax)
fig.tight_layout()
fig.savefig("velocity_components.png")
plt.close(fig)Analysis Functions
scripts/analyze.py bundles these against a params.npz written by runner.py. It infers the
physical grid spacing from the saved coordinates, so the derivatives come out per unit length:
import sys
sys.path.insert(0, "skills/openpiv/scripts")
from analyze import PIVAnalyzer
piv = PIVAnalyzer("results/params.npz")
vorticity = piv.compute_vorticity() # dv/dx - du/dy
exx, eyy, exy = piv.compute_strain()
stats = piv.compute_statistics() # excludes flagged and masked vectors
piv.plot_vector_field(save_path="quiver.png")The standalone forms, if you would rather compute them inline:
Vorticity
def compute_vorticity(u, v, dx=1.0, dy=None):
"""Out-of-plane vorticity dv/dx - du/dy. Pass the physical grid spacing, not 1.0."""
dy = dx if dy is None else dy
return np.gradient(v, dx, axis=1) - np.gradient(u, dy, axis=0)For the single-pass extended-search grid, spacing is
(search_area_size - overlap) / scaling_factor in physical units; it reduces to
(window_size - overlap) / scaling_factor only when the two window sizes match.
Prefer differences of the saved x and y coordinates, especially after multipass
processing. Leaving dx=1.0 yields vorticity per grid cell, not per unit length.
See OpenPIV coordinate generation.
Sign convention: the CLI writes y = image_height / scaling_factor - y and negates v,
keeping rows in image order. Saved y decreases down the array. The standalone forms need
signed dy (negative here), or use PIVAnalyzer, which reads orientation. Positive dy changes
the y-derivative contribution and can even cancel real rotation.
Strain Rate
def compute_strain(u, v, dx=1.0, dy=None):
"""Return (exx, eyy, exy) of the 2D strain-rate tensor."""
dy = dx if dy is None else dy
du_dx = np.gradient(u, dx, axis=1)
du_dy = np.gradient(u, dy, axis=0)
dv_dx = np.gradient(v, dx, axis=1)
dv_dy = np.gradient(v, dy, axis=0)
return du_dx, dv_dy, 0.5 * (du_dy + dv_dx)Turbulence Statistics
def compute_statistics(u, v):
"""Single-frame spatial statistics. NOT Reynolds decomposition."""
u_prime = u - np.nanmean(u)
v_prime = v - np.nanmean(v)
rms_u, rms_v = np.nanstd(u_prime), np.nanstd(v_prime)
return {
"u_mean": np.nanmean(u),
"v_mean": np.nanmean(v),
"rms_u": rms_u,
"rms_v": rms_v,
"tke": 0.5 * (rms_u**2 + rms_v**2),
}Caveat: subtracting the spatial mean of one frame measures spatial variance, which equals
turbulent intensity only under justified homogeneity/ergodicity assumptions. Reynolds decomposition
needs an ensemble: subtract its mean field from each realization. The legacy tke key is only
two-component spatial variance energy, not full turbulent kinetic energy. The analyzer excludes
flagged vectors by default; compute_statistics(include_interpolated=True) includes repaired ones.
CLI Usage
# Basic run
python skills/openpiv/scripts/runner.py \
--image img1.bmp --image img2.bmp --output_dir results --verbose
# Tuned parameters with dynamic masking
python skills/openpiv/scripts/runner.py \
--image frame_a.bmp \
--image frame_b.bmp \
--output_dir results \
--window_size 32 \
--overlap 12 \
--search_area 38 \
--dt 0.02 \
--scaling 96.52 \
--threshold 1.05 \
--mask dynamic \
--mask_method intensity \
--verboseCLI Options
| Option | Default | Description |
|---|---|---|
--image | required | Image file; specify exactly twice for the pair |
--output_dir | results | Output directory (created if absent) |
--window_size | 32 | Interrogation window size (px) |
--overlap | 12 | Search-grid overlap (px), nonnegative and less than search area |
--search_area | 38 | Search area size (px), must be ≥ --window_size |
--dt | 0.02 | Time between frames (s) |
--scaling | 96.52 | Scaling factor, pixels per physical unit (e.g. px/mm) |
--threshold | 1.05 | peak2peak signal-to-noise threshold |
--mask | none | none or dynamic (openpiv.preprocess.dynamic_masking) |
--mask_method | intensity | Only intensity is usable in 0.26.1; edges is refused |
--backend | scipy | scipy, auto, or rust; Rust requires its extension |
--drop_invalid | off | NaN out flagged vectors instead of keeping interpolated values |
--verbose | off | Print progress messages |
Verify an install end to end against OpenPIV's own bundled image pair:
python skills/openpiv/scripts/run_example.py --output_dir /tmp/openpiv-demoOutput Files
vectors.txt — tab-delimited,
%.4eformatted, with a# x y u v flags maskcomment headerparams.npz —
x,y,u,v,flags,mask,s2n, timing/calibration/window parameters, requested backend, and OpenPIV version. Coordinates use physical units; velocity uses units/s.vector_field.png — vector field drawn over the first frame
# x y u v flags mask
2.1757e-01 3.5226e+00 -6.2220e-02 -2.7081e+00 0.0000e+00 0.0000e+00
4.8695e-01 3.5226e+00 -3.1587e-01 -2.9800e+00 0.0000e+00 0.0000e+00flags is float, 0 for valid and 1 for flagged. mask=1 marks an excluded object region,
independently of flags. Preserve both with the measured field.
Best Practices
Parameter Selection
Window size — 32×32 suits most cases. 64/128 for better correlation at coarser resolution; 16/24 for finer resolution at the cost of noise.
Overlap — 50–75% of window size.
Threshold — raise it to reject more vectors; always re-tune after switching
sig2noise_method.Scaling factor — calibrate against a known reference such as a calibration grid, and keep the units straight (
96.52in OpenPIV'stest1tutorial data is px/mm).
Image Quality
Particles visible and evenly distributed, 5–10 per interrogation window
No saturated or overexposed regions
Minimal background noise; consider background subtraction across a run
Processing Tips
Start from the defaults, then tune against the vector field you get.
Inspect the
s2ndistribution — a low median means poor correlation, not a bad threshold.Visualize early; obvious problems (uniform vectors, edge artifacts) show up immediately.
Use multi-pass (
windef) for flows with large velocity gradients or displacements.Mask reflections and solid boundaries rather than letting them generate vectors.
Resources
references/
advanced_algorithms.md— correlation and subpixel methods, multi-pass window deformation,PIVSettingsfields, 3D and phase-separation modules
Load the reference when detailed algorithm or settings information is needed.
Files
- ATTRIBUTION.mdAdded by AutoGineer900 B
- SKILL.md18.6 KiB
- references/advanced_algorithms.md14.8 KiB
- scripts/__init__.pyScript29 B
- scripts/analyze.pyScript6.1 KiB
- scripts/run_example.pyScript2.8 KiB
- scripts/runner.pyScript10.1 KiB
references/advanced_algorithms.md
Advanced OpenPIV Algorithms and Settings
Reviewed against the released openpiv 0.26.1 source and SciPy runtime on 2026-10-01.
Hosted documentation can lag the wheel; for example its API listing omits the new backend argument.
Correlation methods
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 padded to a power of two covering the full correlation, then cropped back to search-window size. Reduces wrap-around; displacement range is still limited by that crop and particle overlap. |
Use these two names. "direct" appears in some legacy docstrings but is not implemented by the
SciPy extended_search_area_piv correlation path. Do not offer it as an option.
Both methods support extended search; larger extreme velocities alone do not establish aliasing.
The bundled CLI chooses "linear" with normalized_correlation=True for extended windows,
following the API's normalization requirement. Validate either choice using known displacements.
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 batches peak extraction and signal-to-noise calculations. Both paths already
hold the correlation stack; edge/degenerate peak behavior can differ. It remains False in
extended_search_area_piv, but PIVSettings.use_vectorized defaults to True since 0.25.5.
backend="scipy" selects the tested reference path. "auto" can use installed Rust components;
in 0.26.1 the FFT dispatcher still selects SciPy unless "rust" is explicit. "rust" requires
openpiv_rust and raises ImportError when absent. Record backend and package versions; do not
assume every wheel contains the extension or infer speed/numerical parity from release notes.
Subpixel peak fitting
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.
Signal-to-noise measures
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 bothextended_search_area_pivandPIVSettings. Values run higher and depend on map size."peak2peak"— first peak divided by the second-highest peak, excluding awidth-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.
Multi-pass window deformation (openpiv.windef)
windef.simple_multipass(frame_a, frame_b, settings) runs the full loop:
windef.first_pass— coarse correlation onwindowsizes[0].validation.typical_validation— applies every enabled check insettingsat once.filters.replace_outliers— fills flagged vectors.windef.multipass_img_deformfor iterations1 .. num_iterations-1— deforms the interrogation windows using the previous pass as a predictor, then re-correlates on the next smaller window.Remaining NaNs filled with zeros;
transform_coordinatesapplied.
It returns (x, y, u, v, flags) with coordinates in pixels and velocities in pixels per frame.
simple_multipass (and alias multigrid_windef) ignore settings.dt and
settings.scaling_factor; 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 dtBatch windef.piv(settings) does apply both before saving. The array convenience wrapper always
validates/replaces the first pass, does not perform the batch image preprocessing or between-pass
smoothing, and discards the final masked-array mask when returning .data. For masks, full switch
control, and preserved quality information use the batch pipeline or manage lower-level passes.
Always supply settings: the no-settings wrapper resets window lists to two entries but leaves
num_iterations=3. Construction checks list lengths and overlap; changing attributes bypasses those
checks, and the number of iterations must still match the requested passes.
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 reference
windef.PIVSettings() is a dataclass. Prefer constructor keywords so its consistency checks run.
This table describes batch settings; do not assume the simple array wrapper uses every field.
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 |
The 0.26.1 batch dispatcher checks "edge" (singular), while dynamic_masking expects "edges";
the latter also has the uint8-indexing bug described in SKILL.md. Neither spelling is a safe edge
masking recipe for this release. Use intensity/static masks and verify excluded regions on outputs.
Correlation
| Field | Default |
|---|---|
correlation_method | "circular" (or "linear") |
backend | "auto" (or "scipy", "rust") |
normalized_correlation | False |
windowsizes | (64, 32, 16) |
overlap | (32, 16, 8) |
num_iterations | 3 |
subpixel_method | "gaussian" |
use_vectorized | True |
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; applied by batch piv, ignored by simple_multipass |
scaling_factor | 1.0 | Pixels per physical unit; same batch/array distinction |
Validation — thresholds consumed by validation.typical_validation; the batch runner controls
whether the first pass is validated
| 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" |
n_cpus | 1 (batch image-pair multiprocessing) |
Volumetric PIV (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)
)
# 0.26.1's FFT direction differs from 2D. These components align with
# increasing column, row, depth coordinates (verify on a known translation).
column_velocity, row_velocity, depth_velocity = -u, v, wInputs 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.
The 0.26.1 3D FFT reverses frame B, unlike the 2D path. Positive input translations along
(row, column, depth) give raw (u, v, w)=(-dcolumn, +drow, +ddepth)/dt, confirmed for both
shift directions. Do not reuse the 2D sign conversion. The mapping above aligns velocities with
the increasing column/row/depth coordinates returned by get_coordinates; apply a separately
calibrated laboratory-axis transform to both positions and velocities. The grid stride uses
window_size-overlap, unlike the 2D extended
search stride. sig2noise_method=None returns three arrays here, rather than the 2D API's three
arrays with a NaN s2n. The 32³ recipe ran on synthetic volumes; smaller positive and negative
translations established the component signs. Real volumetric experiments remain unvalidated.
Phase separation (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.
Size cutoffs are segmented region areas in pixels, not particle diameters; strict inequalities
exclude equality. Brightness and I_sat must match the input intensity scale (the example is 8-bit).
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.
Choosing an approach
| 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 |
Review sources and verification
Validation, replacement, and smoothing
Tests use generated particle translations, analytic velocity fields, mask persistence, fractional intensities, multipass units, NaN smoothing, and small volumetric/phase-separation examples. These are API/numerical checks, not validation of a real experiment's calibration, uncertainty, tracer fidelity, time/ensemble convergence, or particle-phase classification. Rust was unavailable in the tested environment; its execution and claimed speedup were not verified. No remote service, authentication, endpoints, or pagination are involved in local OpenPIV processing.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Needs Python
The author says: Requires Python 3.10+ with openpiv installed (uv pip install openpiv). numpy, scipy, scikit-image, and matplotlib arrive as dependencies. No network access needed after install.
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- OpenPIV Team
- Publisher
- K-Dense (Community)
- Distributed by
- K-Dense
- Source
- K-Dense-AI/scientific-agent-skills · skills/openpiv · commit a25cf9e
- Licence
- BSD 3-Clause License (redistribution allowed)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by OpenPIV Team or K-Dense.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download openpiv.zip