Reconstruction API (load model, triangulate points, optional image maps)
This page documents a small, usage-oriented API to reconstruct 3D points from stereo correspondences, and a file format that is compatible with future ML usage (small JSON + NPZ weights).
The goal is to make it easy to:
export a calibrated stereo model to disk,
load it back in Python with a single import,
triangulate points (and optionally precompute per-pixel ray maps).
File format: model.json + weights.npz
Stereo models are stored in a directory:
models/<name>/
model.json
weights.npz
model.json: small metadata (schema, image size, disk mapping, rig parameters, NPZ keys).weights.npz: NumPy arrays (Zernike coefficients + stereo rig).
This structure is “ML friendly”: a training loop can treat weights.npz as learnable parameters while keeping the rest in JSON.
API: load + triangulate
The following code is a minimal, commented example using the public API:
import argparse
import json
from pathlib import Path
from typing import Any, Literal
import numpy as np
from stereocomplex.api import load_stereo_central_rayfield
Side = Literal["left", "right"]
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def load_frames(scene_dir: Path) -> list[dict[str, Any]]:
frames_path = scene_dir / "frames.jsonl"
frames: list[dict[str, Any]] = []
for line in frames_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
frames.append(json.loads(line))
return frames
def summarize(vals: list[float]) -> dict[str, float]:
if not vals:
return {"n": 0, "rms": float("nan"), "p50": float("nan"), "p95": float("nan"), "max": float("nan")}
v = np.asarray(vals, dtype=np.float64)
return {
"n": int(v.size),
"rms": float(np.sqrt(np.mean(v * v))),
"p50": float(np.quantile(v, 0.50)),
"p95": float(np.quantile(v, 0.95)),
"max": float(np.max(v)),
}
def _dict_from_ids_xy(ids: np.ndarray, xy: np.ndarray) -> dict[int, np.ndarray]:
ids = np.asarray(ids, dtype=np.int32).reshape(-1)
xy = np.asarray(xy, dtype=np.float64).reshape(-1, 2)
return {int(i): xy[k].astype(np.float64) for k, i in enumerate(ids.tolist())}
def _stack_for_ids(ids: list[int], mapping: dict[int, np.ndarray]) -> np.ndarray:
return np.asarray([mapping[int(i)] for i in ids], dtype=np.float64).reshape(-1, 2)
See also: PUBLIC_API.md for the stability contract and recommended imports.
Optionally, precompute ray directions over the full image grid (useful for real-time pipelines):
dL_map, dR_map = model.ray_direction_maps() # (H,W,3) float32
End-to-end demo on a dataset scene
This demo calibrates a central 3D ray-field from a subset of frames, exports the model, then evaluates it on the scene (triangulation error against GT).
Prerequisites
opencv-contrib-python(required forcv2.aruco)
1) Calibrate + export a reusable model (public API)
from pathlib import Path
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",
nmax=10,
export_model_dir=Path("models/scene0000_rayfield3d"),
)
print(result.report)
2) Apply the model (reconstruction on detected corners)
.venv/bin/python docs/examples/reconstruction_api_demo.py dataset/v0_png \
--split train --scene scene_0000 --max-frames 5 \
--model models/scene0000_rayfield3d
Notes:
This evaluation uses OpenCV ChArUco detections to obtain correspondences in each frame.
GT comparison requires synthetic data with
gt_charuco_corners.npz.If you only want to export refined 2D corners for OpenCV calibration (without ray-field 3D), see
stereocomplex refine-cornersindocs/START_HERE.md.
The same workflow on your own folders
If you have two directories of stereo images instead of a dataset v0 scene, use:
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",
export_model_dir=Path("models/my_calibration"),
)
See also: :doc:BRING_YOUR_OWN_DATA.
Code references
API classes + triangulation:
src/stereocomplex/api/stereo_reconstruction.pyPublic calibration wrappers:
src/stereocomplex/api/calibration.pySave/load model:
src/stereocomplex/api/model_io.pyInternal calibration script kept for paper experiments:
paper/experiments/calibrate_central_rayfield3d_from_images.pyEvaluate an exported model on a scene (API demo):
docs/examples/reconstruction_api_demo.pyEvaluate an exported model on a scene (JSON report):
paper/experiments/eval_exported_stereo_model.py