Bring your own data
This page is the shortest path from:
left/*.pngright/*.pnga known ChArUco board
to:
a baseline OpenCV raw vs Ray2D + OpenCV comparison,
a calibrated StereoComplex model,
optionally a non-central Zernike rayfield model (validated on real CMO hardware),
an exported
models/<name>/model.json + weights.npz,and then
model.triangulate(...)in Python.
What you need
StereoComplex does not require the full dataset v0 format for user data anymore.
For a first trial you only need:
one folder of left images,
one folder of right images,
the ChArUco board definition:
squares_x,squares_ysquare_size_mm,marker_size_mmaruco_dictionary
The current public API assumes:
one left image matches one right image,
images are paired by sorted filename order,
all images have the same resolution,
the board is planar and visible in several poses.
Minimal calibration from two folders
Option A: stay in OpenCV and compare raw vs Ray2D
from pathlib import Path
import stereocomplex as sc
board = sc.CharucoBoardSpec(
squares_x=11,
squares_y=7,
square_size_mm=39.0713,
marker_size_mm=27.3499,
aruco_dictionary="DICT_4X4_1000",
)
raw = sc.fit_opencv_stereo_from_image_dirs(
left_dir=Path("my_data/left"),
right_dir=Path("my_data/right"),
board=board,
method2d="raw",
)
refined = sc.fit_opencv_stereo_from_image_dirs(
left_dir=Path("my_data/left"),
right_dir=Path("my_data/right"),
board=board,
method2d="rayfield_tps_robust",
)
print(raw.report)
print(refined.report)
Use this when your goal is:
standard OpenCV pinhole calibration,
a quick before/after comparison,
and no 3D ray-field model yet.
Option B: fit the StereoComplex 3D backend
from pathlib import Path
import stereocomplex as sc
board = sc.CharucoBoardSpec(
squares_x=11,
squares_y=7,
square_size_mm=39.0713,
marker_size_mm=27.3499,
aruco_dictionary="DICT_4X4_1000",
)
result = sc.fit_stereo_central_rayfield_from_image_dirs(
left_dir=Path("my_data/left"),
right_dir=Path("my_data/right"),
board=board,
method2d="rayfield_tps_robust",
nmax=10,
export_model_dir=Path("models/my_first_calibration"),
)
print(result.report)
model = result.model
What this does:
detect ArUco / ChArUco in each stereo pair,
optionally refine ChArUco corners with the planar second pass,
initialize poses from homographies,
run the central stereo ray-field bundle adjustment,
optionally export a reusable model directory.
Option C: fit a non-central rayfield (validated on real CMO)
Use this when a central/pinhole model leaves systematic ray gaps or reconstruction bias, for example with protective glass, an inclined window, a thick optical stack, or a diopter-like setup.
from pathlib import Path
import stereocomplex as sc
board = sc.CharucoBoardSpec(
squares_x=9,
squares_y=6,
square_size_mm=20.0,
marker_size_mm=15.0,
aruco_dictionary="DICT_4X4_50",
)
fit = sc.fit_stereo_zernike_origin_field_from_image_dirs(
left_dir=Path("my_data/left"),
right_dir=Path("my_data/right"),
board=board,
max_order=4,
method2d="rayfield_tps_robust",
)
print(fit.residual_rms)
left_field = fit.left_field
right_field = fit.right_field
This path fits a Zernike origin field O(u,v) initialized from an OpenCV stereo
calibration. The model is validated on a real CMO microscope (notebook 09). As with any calibration, check results with diverse board poses,
train/test pose splits, and support-aware rayfield metrics before deployment.
For the practical walkthrough, see :doc:NONCENTRAL_FROM_IMAGES. For the
controlled physical oracle and complete BA discussion, see
:doc:PARALLEL_PLATE_ORIGIN_FIELD.
Later: load and triangulate
import numpy as np
import stereocomplex as sc
model = sc.load_stereo_central_rayfield("models/my_first_calibration")
uv_left_px = np.array([[320.0, 240.0]], dtype=float)
uv_right_px = np.array([[318.5, 240.0]], dtype=float)
XYZ_mm, skew_mm = model.triangulate(uv_left_px, uv_right_px)
XYZ_mm is expressed in the left camera frame.
If you already have a dataset v0 scene
Use the dataset-specific public wrapper:
import stereocomplex as sc
result = sc.fit_stereo_central_rayfield_from_dataset(
dataset_root="dataset/v0_png",
split="train",
scene="scene_0000",
max_frames=5,
method2d="rayfield_tps_robust",
export_model_dir="models/scene0000_rayfield3d",
)
This is the stable API equivalent of the older internal calibration scripts.
If you only want to improve OpenCV corners
You do not need the 3D ray-field backend for that.
The low-level public workflow is:
import stereocomplex as sc
board = sc.CharucoBoardSpec(
squares_x=11,
squares_y=7,
square_size_mm=39.0713,
marker_size_mm=27.3499,
aruco_dictionary="DICT_4X4_1000",
)
detections = sc.detect_charuco_corners(image="my_data/left/000000.png", board=board)
refined_xy = sc.refine_charuco_corners(
method="rayfield_tps_robust",
board=board,
detections=detections,
)
You can then feed the refined points into your usual OpenCV calibration code.
Practical notes
method2d="raw"keeps OpenCV ChArUco corners as-is.method2d="rayfield_tps_robust"applies the planar second pass before 3D calibration.min_common_cornerscontrols how many common left/right ChArUco corners are required per stereo pair.max_nfevis the main knob for a quicker smoke test versus a more converged optimization.
Recommended first experiment
Do this first:
run with
max_pairs=3ormax_frames=3,keep
nmax=4ornmax=6,confirm that
result.report.n_initialized_frames >= 2,inspect
result.report.train_skew_p95_mmandresult.report.train_point_to_ray_p95_mm,save the model,
only then increase the number of poses and the basis size.
n_initialized_frames >= 2 only means that the optimizer could start. It is not a
quality criterion. Large training skew or point-to-ray residuals mean the exported
model may be reloadable but geometrically unusable.