Configuration & Types

Type definitions and schema for AquaCal calibration system.

This module defines all dataclasses, type aliases, and custom exceptions used throughout the library. No validation is performed at runtime; shapes are documented in docstrings and type hints.

Coordinate conventions: - World frame: Z-down (into water), origin at reference camera - Camera frame: Z-forward, X-right, Y-down (OpenCV convention) - Interface normal: [0, 0, -1] points up (water toward air)

class aquacal.config.schema.BoardConfig(squares_x, squares_y, square_size, marker_size, dictionary, legacy_pattern=False)[source]

Bases: object

ChArUco board specification.

Parameters:
squares_x

Number of chessboard squares in X direction

Type:

int

squares_y

Number of chessboard squares in Y direction

Type:

int

square_size

Size of each square in meters

Type:

float

marker_size

Size of ArUco markers in meters

Type:

float

dictionary

ArUco dictionary name (e.g., “DICT_4X4_50”)

Type:

str

legacy_pattern

If True, board uses legacy ChArUco pattern with marker in top-left cell. Default False (new pattern with solid square in top-left cell)

Type:

bool

class aquacal.config.schema.CameraIntrinsics(K, dist_coeffs, image_size, is_fisheye=False)[source]

Bases: object

Intrinsic parameters for a single camera.

Parameters:
K

3x3 intrinsic matrix

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

dist_coeffs

Distortion coefficients. For pinhole: length 5 or 8 [k1, k2, p1, p2, k3, …]. For fisheye: length 4 [k1, k2, k3, k4].

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

image_size

Image dimensions as (width, height) in pixels

Type:

tuple[int, int]

is_fisheye

If True, uses equidistant fisheye projection model

Type:

bool

class aquacal.config.schema.CameraExtrinsics(R, t)[source]

Bases: object

Extrinsic parameters for a single camera.

Defines the transformation from world coordinates to camera coordinates.

Parameters:
R

3x3 rotation matrix, world -> camera

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

t

3x1 translation vector, world -> camera

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

property C: ndarray[tuple[int, ...], dtype[float64]]

Camera center in world coordinates.

Returns:

3-element array representing camera center position in world frame

class aquacal.config.schema.CameraCalibration(name, intrinsics, extrinsics, water_z, is_auxiliary=False)[source]

Bases: object

Complete calibration for a single camera.

Parameters:
name

Camera identifier (e.g., “cam0”, “cam1”)

Type:

str

intrinsics

Intrinsic camera parameters

Type:

aquacal.config.schema.CameraIntrinsics

extrinsics

Extrinsic camera parameters (pose in world frame)

Type:

aquacal.config.schema.CameraExtrinsics

water_z

Z-coordinate of the water surface in world frame (meters). Same for all cameras after optimization.

Type:

float

is_auxiliary

If True, this camera was registered post-hoc against fixed board poses (excluded from joint Stage 3/4 optimization).

Type:

bool

class aquacal.config.schema.InterfaceParams(normal, n_air=1.0, n_water=1.333)[source]

Bases: object

Refractive interface (water surface) parameters.

Note: Per-camera interface distances are stored in each CameraCalibration object, not here. This dataclass holds only the shared interface properties.

Parameters:
normal

Unit vector pointing from water toward air (typically [0, 0, -1])

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

n_air

Refractive index of air (default 1.0)

Type:

float

n_water

Refractive index of water (default 1.333 for fresh water at 20°C)

Type:

float

class aquacal.config.schema.CalibrationResult(cameras, interface, board, diagnostics, metadata)[source]

Bases: object

Complete calibration output.

Parameters:
cameras

Dictionary mapping camera names to their calibrations

Type:

dict[str, aquacal.config.schema.CameraCalibration]

interface

Refractive interface parameters

Type:

aquacal.config.schema.InterfaceParams

board

ChArUco board configuration used

Type:

aquacal.config.schema.BoardConfig

diagnostics

Calibration quality metrics

Type:

aquacal.config.schema.DiagnosticsData

metadata

Metadata for reproducibility

Type:

aquacal.config.schema.CalibrationMetadata

Example

>>> from aquacal import run_calibration, save_calibration
>>> result = run_calibration("config.yaml")
>>> print(f"Water surface at Z = {result.cameras['cam0'].water_z:.3f} m")
>>> save_calibration(result, "output/calibration.yaml")

Note

For coordinate system conventions, see the Coordinate Conventions guide.

class aquacal.config.schema.DiagnosticsData(reprojection_error_rms, reprojection_error_per_camera, validation_3d_error_mean, validation_3d_error_std, per_corner_residuals=None, per_corner_camera_labels=None, per_frame_errors=None)[source]

Bases: object

Calibration quality metrics.

Parameters:
reprojection_error_rms

Overall RMS reprojection error in pixels (primary cameras only)

Type:

float

reprojection_error_per_camera

Per-camera RMS reprojection errors (primary cameras only)

Type:

dict[str, float]

validation_3d_error_mean

Mean 3D reconstruction error in meters (holdout set, primary cameras only)

Type:

float

validation_3d_error_std

Standard deviation of 3D errors in meters (primary cameras only)

Type:

float

per_corner_residuals

Optional (N, 2) array of pixel errors for each corner

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]] | None

per_corner_camera_labels

Optional list of camera names parallel to per_corner_residuals

Type:

list[str] | None

per_frame_errors

Optional dict mapping frame index to error value

Type:

dict[int, float] | None

class aquacal.config.schema.CalibrationMetadata(calibration_date, software_version, config_hash, num_frames_used, num_frames_holdout)[source]

Bases: object

Metadata for reproducibility.

Parameters:
  • calibration_date (str)

  • software_version (str)

  • config_hash (str)

  • num_frames_used (int)

  • num_frames_holdout (int)

calibration_date

ISO format date string

Type:

str

software_version

Version of aquacal library used

Type:

str

config_hash

Hash of configuration for reproducibility

Type:

str

num_frames_used

Number of frames used in calibration

Type:

int

num_frames_holdout

Number of frames held out for validation

Type:

int

class aquacal.config.schema.CalibrationConfig(board, camera_names, intrinsic_video_paths, extrinsic_video_paths, output_dir, intrinsic_board=None, n_air=1.0, n_water=1.333, interface_normal_fixed=False, robust_loss='huber', loss_scale=1.0, min_corners_per_frame=8, min_cameras_per_frame=2, frame_step=1, extrinsic_start_frame=0, extrinsic_stop_frame=None, holdout_fraction=0.2, max_calibration_frames=None, refine_intrinsics=False, refine_auxiliary_intrinsics=False, reject_outlier_frames=True, frame_rejection_k=5.0, frame_rejection_floor_px=5.0, frame_rejection_max_fraction=0.25, save_detailed_residuals=True, initial_water_z=None, rational_model_cameras=<factory>, auxiliary_cameras=<factory>, fisheye_cameras=<factory>)[source]

Bases: object

Input configuration for calibration pipeline.

Parameters:
  • board (BoardConfig)

  • camera_names (list[str])

  • intrinsic_video_paths (dict[str, Path])

  • extrinsic_video_paths (dict[str, Path])

  • output_dir (Path)

  • intrinsic_board (BoardConfig | None)

  • n_air (float)

  • n_water (float)

  • interface_normal_fixed (bool)

  • robust_loss (str)

  • loss_scale (float)

  • min_corners_per_frame (int)

  • min_cameras_per_frame (int)

  • frame_step (int)

  • extrinsic_start_frame (int)

  • extrinsic_stop_frame (int | None)

  • holdout_fraction (float)

  • max_calibration_frames (int | None)

  • refine_intrinsics (bool)

  • refine_auxiliary_intrinsics (bool)

  • reject_outlier_frames (bool)

  • frame_rejection_k (float)

  • frame_rejection_floor_px (float)

  • frame_rejection_max_fraction (float)

  • save_detailed_residuals (bool)

  • initial_water_z (dict[str, float] | None)

  • rational_model_cameras (list[str])

  • auxiliary_cameras (list[str])

  • fisheye_cameras (list[str])

board

ChArUco board specification (extrinsic/underwater board)

Type:

aquacal.config.schema.BoardConfig

camera_names

List of camera identifiers

Type:

list[str]

intrinsic_video_paths

Dict mapping camera names to intrinsic calibration videos

Type:

dict[str, pathlib.Path]

extrinsic_video_paths

Dict mapping camera names to extrinsic calibration videos

Type:

dict[str, pathlib.Path]

output_dir

Directory for output files

Type:

pathlib.Path

intrinsic_board

Optional separate board for in-air intrinsic calibration (defaults to None, uses board)

Type:

aquacal.config.schema.BoardConfig | None

n_air

Refractive index of air (default 1.0)

Type:

float

n_water

Refractive index of water (default 1.333)

Type:

float

interface_normal_fixed

Whether to fix interface normal to [0, 0, -1]

Type:

bool

robust_loss

Loss function for optimization (“huber”, “soft_l1”, “linear”)

Type:

str

loss_scale

Scale parameter for robust loss in pixels

Type:

float

min_corners_per_frame

Minimum corners required to use a detection

Type:

int

min_cameras_per_frame

Minimum cameras required to use a frame

Type:

int

frame_step

Process every Nth frame (1 = all frames, default 1)

Type:

int

extrinsic_start_frame

First frame index (inclusive) to process in the extrinsic-stage videos. Applied identically to every extrinsic camera via a single synchronized frame iterator, so frame-index alignment (video sync) is preserved. Use to skip contaminated frames at the start of the extrinsic capture (e.g. board out of water / surface ripples). Does NOT affect intrinsic calibration videos. Default 0.

Type:

int

extrinsic_stop_frame

Frame index (exclusive) to stop processing at in the extrinsic-stage videos. Applied identically to every extrinsic camera via the same synchronized frame iterator, so frame-index alignment (video sync) is preserved. Use to skip contaminated frames at the end of the extrinsic capture (e.g. the board leaving the water). Does NOT affect intrinsic calibration videos. Default None (process to the end).

Type:

int | None

holdout_fraction

Fraction of frames to hold out for validation

Type:

float

max_calibration_frames

Maximum number of frames for Stages 3-4 optimization. None (default) = use all calibration frames. When set, calibration frames are uniformly subsampled to this limit before optimization.

Type:

int | None

refine_intrinsics

If True, Stage 4 jointly refines per-camera focal lengths (fx, fy) and principal points (cx, cy) alongside extrinsics and interface distances. Distortion coefficients are NOT refined. Only enable after Stage 3 converges reliably. Default False (Stage 4 skipped).

Type:

bool

save_detailed_residuals

Whether to save per-corner residuals

Type:

bool

initial_water_z

Optional dict mapping camera names to approximate camera-to-water-surface distances in meters. When None, all cameras default to 0.15m. Doesn’t need to be exact — within 2-3x of the true value is sufficient for good initialization in Stage 3.

Type:

dict[str, float] | None

rational_model_cameras

List of camera names that should use the 8-coefficient rational distortion model instead of the standard 5-coefficient model. Use for wide-angle lenses where 5 coefficients are insufficient.

Type:

list[str]

auxiliary_cameras

List of auxiliary camera names registered post-hoc against fixed board poses. These cameras are calibrated for intrinsics and detected, but excluded from joint Stage 3/4 optimization. Must not overlap with camera_names.

Type:

list[str]

fisheye_cameras

List of camera names that should use the equidistant fisheye projection model. Must be a subset of auxiliary_cameras and must not overlap with rational_model_cameras.

Type:

list[str]

refine_auxiliary_intrinsics

If True, Stage 4b refines auxiliary camera intrinsics (fx, fy, cx, cy) alongside extrinsics. Requires auxiliary_cameras to be set. Independent of refine_intrinsics (which controls primary camera refinement in Stage 4). Distortion coefficients are NOT refined.

Type:

bool

reject_outlier_frames

If True (default), after Stage 3 the pipeline computes per-frame reprojection RMS and drops frames whose RMS is a catastrophic outlier, then re-runs Stage 3 on the cleaned set before Stage 4. This is a no-op on already-clean data (see thresholds below), so the default only affects datasets with genuinely bad frames (e.g. board out of water / surface ripples / mis-detections).

Type:

bool

frame_rejection_k

Relative multiplier on the median per-frame RMS used to flag outliers; a frame is rejected if RMS > k * median (and also above the absolute floor). Default 5.0.

Type:

float

frame_rejection_floor_px

Absolute minimum RMS in pixels a frame must exceed to be eligible for rejection, regardless of the relative bound. Prevents over-rejection when the median RMS is tiny. Default 5.0.

Type:

float

frame_rejection_max_fraction

Guardrail. If the fraction of frames that would be rejected exceeds this, rejection is suppressed entirely and a warning is emitted, so a broadly-broken dataset surfaces loudly rather than being silently gutted. Default 0.25.

Type:

float

class aquacal.config.schema.BoardPose(frame_idx, rvec, tvec)[source]

Bases: object

Pose of ChArUco board in a single frame.

Parameters:
frame_idx

Frame index in the video sequence

Type:

int

rvec

Rodrigues rotation vector (3,)

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

tvec

Translation vector (3,)

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

class aquacal.config.schema.Detection(corner_ids, corners_2d)[source]

Bases: object

ChArUco detection in a single image.

Parameters:
corner_ids

Array of corner IDs detected, shape (N,)

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.int32]]

corners_2d

Array of 2D corner positions in pixels, shape (N, 2)

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

property num_corners: int

Number of detected corners.

Returns:

Count of detected corners

class aquacal.config.schema.PointCorrespondence(point_3d, observations, weight=1.0)[source]

Bases: object

A 3D point with its observed 2D projections across cameras.

Used by refine_calibration() to refine an existing calibration using 3D-to-2D point correspondences from downstream analysis.

Parameters:
point_3d

3D point in world coordinates, shape (3,)

Type:

numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]

observations

Dict mapping camera name to observed pixel coordinate (u, v). Must contain at least 2 cameras.

Type:

dict[str, numpy.ndarray[tuple[int, …], numpy.dtype[numpy.float64]]]

weight

Optional non-negative weight for this correspondence. Default 1.0. Set to 0.0 to soft-disable without removing.

Type:

float

class aquacal.config.schema.CameraDrift(translation_mm, rotation_deg, exceeded)[source]

Bases: object

Per-camera extrinsics drift metrics from refinement.

Parameters:
translation_mm

Translation shift magnitude in millimeters.

Type:

float

rotation_deg

Rotation shift magnitude in degrees.

Type:

float

exceeded

True if either translation or rotation exceeds its threshold.

Type:

bool

class aquacal.config.schema.ValidationReport(holdout_reproj_error, triangulation_consistency_before, triangulation_consistency_after, camera_drifts, accepted, summary)[source]

Bases: object

Structured validation report for a calibration refinement.

Parameters:
holdout_reproj_error

RMS reprojection error on held-out correspondences (pixels).

Type:

float

triangulation_consistency_before

Mean ray intersection residual before refinement (meters).

Type:

float

triangulation_consistency_after

Mean ray intersection residual after refinement (meters).

Type:

float

camera_drifts

Per-camera extrinsics drift, mapping camera name to CameraDrift.

Type:

dict[str, aquacal.config.schema.CameraDrift]

accepted

True if all thresholds pass, False if any exceeded.

Type:

bool

summary

Human-readable explanation of the accept/reject decision.

Type:

str

class aquacal.config.schema.RefinementResult(result, validation_report, accepted)[source]

Bases: object

Result of refine_calibration() with optional validation.

Parameters:
result

The refined CalibrationResult.

Type:

aquacal.config.schema.CalibrationResult

validation_report

Structured validation metrics, or None if validate=False was passed.

Type:

aquacal.config.schema.ValidationReport | None

accepted

True/False recommendation, or None if validate=False.

Type:

bool | None

class aquacal.config.schema.FrameDetections(frame_idx, detections)[source]

Bases: object

Detections across all cameras for a single frame.

Parameters:
frame_idx

Frame index in the video sequence

Type:

int

detections

Dict mapping camera names to Detection objects

Type:

dict[str, aquacal.config.schema.Detection]

property cameras_with_detections: list[str]

List of camera names that detected the board in this frame.

Returns:

List of camera names

property num_cameras: int

Number of cameras that detected the board in this frame.

Returns:

Count of cameras with detections

class aquacal.config.schema.DetectionResult(frames, camera_names, total_frames)[source]

Bases: object

All detections from a video set.

Parameters:
frames

Dict mapping frame indices to FrameDetections

Type:

dict[int, aquacal.config.schema.FrameDetections]

camera_names

List of all camera names in the dataset

Type:

list[str]

total_frames

Total number of frames processed

Type:

int

get_frames_with_min_cameras(min_cameras)[source]

Return frame indices where at least min_cameras see the board.

Parameters:

min_cameras (int) – Minimum number of cameras required

Returns:

List of frame indices meeting the criterion

Return type:

list[int]

exception aquacal.config.schema.CalibrationError[source]

Bases: Exception

Base class for calibration-related errors.

exception aquacal.config.schema.InsufficientDataError[source]

Bases: CalibrationError

Raised when there isn’t enough data for calibration.

exception aquacal.config.schema.ConvergenceError[source]

Bases: CalibrationError

Raised when optimization fails to converge.

exception aquacal.config.schema.ConnectivityError[source]

Bases: CalibrationError

Raised when pose graph is not connected (cameras cannot be linked).