# Stereo calibration workflow This is the practical, step-by-step guide from calibration images to a 3-component $(u, v, w)$ velocity field. The theory behind every step — calibration models, Soloff reconstruction, disparity correction — is on the [stereo theory page](stereo_calibration_mapping.md). `Examples/stereo_tutorial.py` runs this whole pipeline on a public dataset (the 4th PIV Challenge case E, downloaded automatically on first run by {func}`~dpivsoft.stereo_DPIV.download_pivchallenge_E`, ~197 MB). ## What you need - **Calibration images of a dual-plane target** (TSI or LaVision) from both cameras. A dual-plane target provides two $Z$ levels per image, which is already the minimum for the reduced (9-term) model; the full 19-term Soloff model needs **≥ 3 distinct $Z$ planes**, so traverse the target and take one image per position. The flat single-plane *Quadricle* target cannot constrain the out-of-plane parallax: the Soloff maps come out NaN and the GUI reports "Soloff maps: NOT available". - **Flow image pairs from both cameras**, simultaneous between cameras. - The PIV parameters you would use for a planar measurement (both cameras share the same `Parameters`). ## 1. Launch the calibration GUI ```python import dpivsoft.stereo_gui as gui gui.calibration_GUI() # blocks until the window is closed ``` ## 2. Enter the target geometry In the options panel, set the dot spacings and the camera arrangement: - `dx` — in-row dot pitch of one level (mm). - `dy` — spacing between **adjacent image rows** (mm). For LaVision targets the two $Z$ levels interleave row by row, so this is **half** the per-level pitch — a LaVision Type #21 plate (15 mm pitch) needs `dx = 15`, `dy = 7.5`, `dz = 3`. Getting `dy` wrong is undetectable in the fit RMS (the polynomial absorbs a linear stretch) but scales the reconstructed $Y$ and $v$ — the one field worth double-checking. - `dz` — the out-of-plane offset between the target's two dot levels (mm). - **Camera configuration** — *Front-Front* (both cameras on the same side of the sheet) or *Front-Back* (opposite sides). This is baked into the fitted maps and saved with the calibration. ## 3. Per image: detect, sort, save For each calibration image (each camera, each traverse position): 1. **Load** the image and adjust the threshold / cleaning until the dots are isolated blobs. 2. **Get centroids** — dots clipped by the image border are dropped automatically (their centroids are biased). 3. **Sort points** — assigns each dot absolute lattice `(row, col)` labels by a predictive lattice walk seeded at the origin fiducial. Missing or undetected dots stay holes instead of shifting the labels of every dot after them; rotations up to ~25° and perspective are handled. 4. **Calibration mesh** — assigns world coordinates $(X, Y, Z)$ from the lattice labels, the spacings entered in §2, and the plane's $Z$ position. 5. **Save the CPT** — a plain-text file with one `x y X Y Z` line per dot. CPTs are the checkpoint of the workflow: reloading them skips detection and sorting entirely on later runs. For a multi-plane calibration, save one CPT per plane and load them together with the GUI's *load multi CPT* option so the fit sees the full depth range. ## 4. Fit the calibration ("Soloff Calculation") The toolbar button opens a popup to choose the model and run {func}`~dpivsoft.stereo_DPIV.stereo_calibration`: - **reduced** (9-term, default) — needs ≥ 2 distinct $Z$ planes. - **full** (19-term classic Soloff) — needs ≥ 3 distinct $Z$ planes; the GUI refuses to run it with fewer. Worth it when the optics have real lens distortion or $Z$-curvature — see [when does the full model help?](stereo_calibration_mapping.md#8-when-does-the-full-model-help) The results line reports the model, the forward-fit RMS (mm), the Soloff-map reprojection RMS (px), and whether the Soloff maps are available. Save the calibration `.npz`; it carries everything reconstruction needs, including the camera configuration and the model (auto-detected from the coefficient length when loaded). ## 5. PIV on the raw images Process each camera's pair sequence with the ordinary 2-D pipeline (GPU or CPU), using the **same** `Parameters` for both cameras. Do **not** dewarp the images first — the Soloff reconstruction works directly on raw-image displacements. ## 6. Reconstruct the three components ```python from dpivsoft.Classes import Parameters import dpivsoft.stereo_DPIV as Stereo calibration = Parameters.Stereo_Calibration("stereo_calibration.npz") X, Y, U, V, W = Stereo.soloff_velocity( x_l, y_l, u_l, v_l, # left-camera PIV field (raw-image pixels) x_r, y_r, u_r, v_r, # right-camera PIV field calibration, img_shape, delta_z=None) # None = reconstruct at Z=0 ``` Sign convention: positive $w$ points toward the right camera. ## 7. Optional: Δz self-calibration Even a good calibration leaves a small offset between the laser sheet and the calibration plane, which biases $w$. To correct it (theory in {ref}`§10 of the stereo page `): 1. Build the common world grid ({func}`~dpivsoft.stereo_DPIV.make_world_grid`) and dewarp a series of **same-instant** frame pairs from both cameras onto it at $Z = 0$ ({func}`~dpivsoft.stereo_DPIV.backproject_folder`). 2. Ensemble cross-correlate the two dewarped series ({func}`~dpivsoft.stereo_DPIV.compute_disparity`) — frame $i$ of camera 1 against frame $i$ of camera 2, *not* the consecutive PIV pairing. 3. Convert the disparity to a $\Delta z(X, Y)$ field ({func}`~dpivsoft.stereo_DPIV.disparity_to_delta_z`), with `surface_order=2` recommended (fits a quadratic surface: keeps sheet tilt and curvature, rejects per-point noise). 4. Pass the field to `soloff_velocity(..., delta_z=...)`. Iterating (re-dewarp at the accumulated $\Delta z$, re-measure) should shrink the residual disparity each pass — {func}`~dpivsoft.stereo_DPIV.disparity_convergence_report` prints the check. `stereo_tutorial.py` implements this loop behind the `RUN_DISPARITY` / `DISP_SURFACE_ORDER` / `N_DISP_ITER` constants. ## Status and honest caveats The stereo pipeline is functional but newer than the 2-D core. A synthetic end-to-end validation (known $w$ field → calibration → PIV → reconstruction) recovers $w$ to within a few percent, and the disparity self-calibration converges (residual shrinks each iteration), which confirms its sign and self-consistency. Still open: the absolute $\Delta z$ / $w$ magnitude has not been checked against an independent experimental ground truth, and the Front-Back $w$ sign *cannot* be verified synthetically — it awaits a real known-$w$ case. Treat quantitative $w$ from Front-Back arrangements with appropriate care.