Global Coordinates
Express every camera in one shared physical reference frame. How this is done depends on which calibration board you use.
Quick Recipe
opinionated defaults -- full reference below- 1.Open the Scale Factor calibration tab to reveal the Calibration Image Viewer, then toggle Global Coords on in the settings bar. This panel lives on that tab only.
- 2.On Camera 1: click Set Origin and click your physical (0,0) reference point. Enter the physical X/Y in mm if it's not literally at (0,0).
- 3.Still on Camera 1: click Pick F1 and click a feature visible in both Camera 1 and Camera 2. Navigate to Camera 2 and click the same physical point with Pick F1.
- 4.For more cameras, each middle camera picks F1 (shared with the previous camera) and F2 (shared with the next). The last camera only needs F1.
- 5.Click Compute + Save Global Frame to bake the shared offset into every camera model. Only then does Calibrate Vectors emit coordinates in the shared frame. There is no mirror or flip control here: the axis directions come from each camera's own calibration.
Two paths, split by board type
When you process more than one camera, the fields only line up if every camera reports its vectors in a single common frame. PIVTOOLs reaches that frame in two different ways, and which one applies is decided entirely by the calibration board.
| Board type | Mechanism | How the shared frame arises |
|---|---|---|
| Dotboard / ChArUco | Joint multi-camera calibration | One shared board solved jointly. The shared frame is intrinsic to the solve -- nothing is baked separately. |
| Scale factor | Multi-camera global frame | Cameras calibrated independently, then a datum + overlap-pair chain bakes a world_offset_mm into each model. |
Which one do I get? You do not choose between them. If you calibrate with a dotboard or ChArUco board, you get the joint solve. If you calibrate with a scale factor, you get the global-frame chain. The rest of this page documents each in turn.
Joint multi-camera model (dotboard / ChArUco)
When every camera observes the same physical board, the calibration solves all cameras together into one shared world frame. This is the system-aware model. A single solve recovers per-camera intrinsics, per-(camera, view) poses, and one released board that every camera agrees on. Because the world frame is part of the solve itself, the cameras are in the same frame by construction -- cross-camera agreement is zero, the DaVis-equivalent result.
- One shared board with a global dot index -- every camera looks at the same physical target
- Per-camera intrinsics plus per-(camera, view) poses solved in a single joint optimisation
- The shared world frame is intrinsic to the solve, not baked on afterwards
- No world_offset_mm and no separate alignment step -- there is nothing to align
- Cross-camera agreement is 0 by construction (DaVis-matching)
In the GUI
This is automatic. Put two or more cameras into the Dotboard or ChArUco calibration tab and the solve is always joint -- there is no toggle to enable (a single camera is a mono solve in the same tab). The result is a JointRecord. Dotboard joint calibration is driven from the GUI because it needs the interactive datum and cross-camera picks of the guided wizard (next section), which are saved to a sidecar inputs.mat beside the model.
Tolerant detection + caching. A view that fails detection is dropped and reported, never fatal -- a camera only fails if no image detects. Detections are cached in memory and persisted in the inputs.mat sidecar, so previews reuse them; the Re-detect button forces a refresh after the images change.
Dotboard joint calibration requires the interactive clicks above, so it runs from the GUI only. The CLI detect-joint command is ChArUco-only.
On the CLI (ChArUco only)
# Joint ChArUco solve across cameras into one shared world frame
pivtools-cli detect-joint --cameras 1,2,3 \
--model-type pinhole \
--board-release full3d| Flag | Description | Default |
|---|---|---|
| --cameras | Cameras to include in the joint solve | — |
| --source | Calibration source (ChArUco) | — |
| --model-type | pinhole | polynomial | pinhole |
| --board-release | full3d | z_only | none -- how much of the board geometry is freed | full3d |
| --model-type | Output |
|---|---|
| pinhole | One JointRecord at <root>/joint_<board>/model/joint_model_pinhole.mat |
| polynomial | Per-camera polynomial records |
No YAML to configure. The joint path does not use the global_coordinates datum + overlap-pair block below -- its datum and cross-camera ties come from the guided wizard and persist in inputs.mat, and the shared frame is part of the solve itself.
Guided wizard -- Set Global Coordinates
On the dotboard joint path, all interactive picking runs through one auto-advancing wizard. Press Set Global Coordinates and follow the banner -- there are no separate origin, link, or rescue buttons.
The sequence
- 1Datum world frame first -- click Origin, +X, +Y on the datum camera's datum view (each click snaps to the nearest detected dot); type the origin mm in the wizard panel
- 2Then, per calibration frame: click the origin dot in camera 1
- 3Click 2 shared dots in camera 1
- 4Click the same 2 dots in camera 2 -- the bridge auto-commits once enough pairs are picked
- The viewer auto-navigates to the awaited camera and frame; if you browse elsewhere mid-walk, picking pauses with a hint until you return -- a click is never attributed to the wrong image
- Markers are persistent and colour-coded: the origin is green, shared dots are coloured by pick order with the same colour in both cameras (dot 1 in camera 1 matches dot 1 in camera 2)
- Bridges walk outward from the datum camera to each adjacent camera (2 from 1, 3 from 2, ...), matching a linear rig's real overlap chain
- Skip a step if a dot is not visible in a view; re-running the wizard skips views that are already anchored
ChArUco joint needs no wizard. Corner ids resolve the global grid with zero clicks. The origin of the shared frame is the corner-id default -- picking a chosen corner as the origin is not yet available on the joint ChArUco path.
Global frame (scale factor)
Scale-factor calibration produces one model per camera, each in its own local frame. The global-frame step ties them together. It takes a datum camera and a chain of overlap pairs, computes the per-camera shift needed to put them all in one frame, and bakes a world_offset_mm into each model.
Coordinates only. The offset is applied at apply-calibration time as a pure constant translation of the coordinate grid. Because it is constant, velocities and Reynolds stresses are unaffected -- only where each field sits in space changes.
Regenerating any camera's model builds a fresh world frame, so its baked world_offset_mm is cleared. Re-save the global frame after recalibrating any camera.
Datum point
The datum is a pixel on the datum camera that defines where the physical origin sits. It carries physical coordinates (in mm), so you can place the origin on the datum feature or offset it from there.
Overlap pairs
Each overlap pair names the same physical feature as seen by two cameras: pixel_on_a on camera_a and pixel_on_b on camera_b. Chaining pairs from the datum camera outward fixes every camera's shift relative to the shared origin.
In the GUI
This is a deliberate step on the Scale Factor tab (shown when more than one camera is configured). After calibrating the cameras, pick the datum and overlap points on the image and use Compute + Save Global Frame to write the datum/overlap configuration and bake the offsets into the models. The saved per-camera offsets in mm are listed on the tab.
On the CLI
The global-frame command reads the datum and overlap pairs from the calibration settings sidecar at <source>/calibration/settings.yaml (normally written by the GUI). The example below shows the multi-camera scale-factor sequence end to end.
# 1. Calibrate each camera independently
pivtools-cli scale-factor --px-per-mm <v> --origin <x> <y>
# 2. Bake the shared frame into every model
# (needs global_coordinates in the settings sidecar)
pivtools-cli global-frame --board scale_factor
# 3. Emit coordinates in the shared frame
pivtools-cli apply-calibration --board scale_factor --all-paths| global-frame flag | Description |
|---|---|
| --source | Calibration source to operate on |
| --board | charuco | dotboard | scale_factor |
| --model-type | Camera model type |
The datum and overlap pairs must exist in the settings sidecar at <source>/calibration/settings.yaml before global-frame runs. Set them in the GUI, or edit the sidecar directly using the block below. If datum_pixel is missing the command stops and names the file it expected to find.
YAML Configuration
The datum and overlap configuration is a top-level global_coordinates block in the per-source settings sidecar, not in config.yaml. Anything written under calibration: in the config is stripped on the next save. It applies to the global-frame path only: the joint solve shares a frame intrinsically and needs no such block.
global_coordinates:
enabled: true
datum_camera: 1 # Camera holding the origin
datum_pixel: [512.0, 384.0] # Pixel position of origin on datum camera
datum_physical: [0.0, 0.0] # Physical coordinates (mm) at datum
datum_frame: 1
overlap_pairs:
- camera_a: 1
camera_b: 2
pixel_on_a: [950.0, 400.0] # Shared feature pixel on camera_a
pixel_on_b: [120.0, 400.0] # Same feature pixel on camera_b
- camera_a: 2
camera_b: 3
pixel_on_a: [930.0, 410.0]
pixel_on_b: [100.0, 405.0]Configuration Fields
| Field | Type | Description |
|---|---|---|
| enabled | bool | Enable the global-frame chain |
| datum_camera | int | Camera that holds the origin |
| datum_pixel | [x, y] | Pixel position of the origin on the datum camera image |
| datum_physical | [x, y] | Physical coordinates (mm) at the datum point |
| datum_frame | int | Calibration frame used for the datum pixel-to-physical conversion |
| overlap_pairs | list | Chain of shared-feature pairs linking the cameras |
| overlap_pairs[].camera_a | int | First camera in the pair |
| overlap_pairs[].camera_b | int | Second camera in the pair |
| overlap_pairs[].pixel_on_a | [x, y] | Shared feature pixel on camera_a |
| overlap_pairs[].pixel_on_b | [x, y] | Same feature pixel on camera_b |
CLI quick reference
Pick the path by board type. Dotboard and ChArUco get the joint solve; scale factor gets the global-frame chain.
Dotboard / ChArUco (joint)
In the GUI, two or more cameras in the calibration tab is always a joint solve -- no extra command. On the CLI, ChArUco joint calibration uses detect-joint (see the joint section above). Dotboard joint has no headless path.
Scale factor (global frame)
# Per camera
pivtools-cli scale-factor --px-per-mm <v> --origin <x> <y>
# Bake shared frame into models
pivtools-cli global-frame --board scale_factor
# Emit coordinates in the shared frame
pivtools-cli apply-calibration --board scale_factor --all-pathsNext: Create Videos
Visualise your aligned, calibrated velocity fields with animated videos.
Continue to Video Maker