Serialization and Runtime-Dynamic Types
The generic Camera<S, P, D, Sm, K> type provides compile-time composition of camera stages. However, for JSON serialization (session checkpointing, data exchange) and runtime-dynamic camera construction, calibration-rs provides an enum-based parameter system.
CameraParams
The CameraParams struct holds camera parameters as serializable enums:
#![allow(unused)] fn main() { pub struct CameraParams { pub projection: ProjectionParams, pub distortion: DistortionParams, pub sensor: SensorParams, pub intrinsics: FxFyCxCy<Real>, // fx, fy, cx, cy } }
The projection, distortion and sensor components are enums of supported variants (serialized with a "type" tag in snake_case); the intrinsics are the plain {fx, fy, cx, cy} struct:
#![allow(unused)] fn main() { pub enum ProjectionParams { Pinhole } pub enum DistortionParams { None, BrownConrady5 { params: BrownConrady5<Real> }, // k1, k2, k3, p1, p2, iters Rational { params: RationalPolynomial<Real> }, // k1..k6, p1, p2, iters ThinPrism { params: ThinPrism<Real> }, // Brown-Conrady + s1..s4 Division { lambda: Real }, // Fitzgibbon division model } pub enum SensorParams { Identity, Homography { h: [[Real; 3]; 3] }, Scheimpflug { params: ScheimpflugParams }, // tilt_x, tilt_y } }
The params payloads are flattened in JSON, so a Brown-Conrady camera serializes as {"type": "brown_conrady5", "k1": ..., "k2": ..., ...}.
The enums are #[non_exhaustive]: new models arrive in non-breaking releases, so a match on one needs a wildcard arm. Code that should work for every model reads the values instead, in the order the model's kind names them:
#![allow(unused)] fn main() { let d = &camera.distortion; for (name, value) in d.kind().param_names().iter().zip(d.values()) { println!("{name} = {value}"); } let adjusted = d.with_values(&new_values)?; // same model, new coefficients let fresh = DistortionParams::from_values(DistortionKind::Rational8, &values)?; }
ProjectionParams and SensorParams have the same kind(), values() and from_values(), and DistortionKind::ALL (likewise for projections and sensors) lists the supported models.
Building a Camera from Parameters
The build() method constructs a concrete CameraModel from serialized parameters. It returns an error if a SensorParams::Homography matrix is not invertible (reachable with hand-edited or corrupted files):
#![allow(unused)] fn main() { let camera = params.build()?; }
This is used internally by the session framework to reconstruct cameras from JSON-checkpointed state.
In Python
vc.CameraModel is the built model, made from a result's camera (a vc.Camera: intrinsics plus projection, distortion and sensor) or a CameraParams payload, with any model in each slot. Its methods are vectorized over numpy arrays and release the GIL for large ones:
import numpy as np
import vision_calibration as vc
cam = vc.CameraModel(result.camera)
pixels = cam.project(points_cam) # (N, 3) camera-frame points -> (N, 2)
origins, directions = cam.unproject(pixels) # (N, 3) rays; pinhole directions have z = 1
ideal = cam.undistort(pixels) # (N, 2) in a distortion-free pinhole image, same K
undistort removes the distortion and the sensor tilt, so the output is where each pixel lands in an ideal pinhole image with the same intrinsics. A point behind the camera projects to NaN.
Reading the Result
The calibrated planar-intrinsics parameters (PlanarIntrinsicsParams, export.params) hold the camera in their camera field and the per-view poses in camera_se3_target:
#![allow(unused)] fn main() { export.params.camera.intrinsics // FxFyCxCy<f64> export.params.camera.distortion // DistortionParams: match on the model export.params.build_camera()? // CameraModel, ready to project }
Use in Session Framework
CameraParams appears throughout the pipeline:
- Export records store calibrated parameters as
CameraParams - Session JSON serializes intermediate and final camera parameters
This provides a stable, version-safe serialization format decoupled from the generic type system.