Metadata-Version: 2.4
Name: pyvernier
Version: 0.0.1.dev1
Summary: Vernier — Geometric calibration toolkit for multi-camera and multi-sensor systems
Keywords: calibration,computer-vision,robotics,bundle-adjustment,ceres,sophus,camera-calibration,extrinsics
Author-Email: Vistralis Labs <support@vistralis.org>
License-Expression: Apache-2.0
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Classifier: Topic :: Scientific/Engineering :: Mathematics
Project-URL: Homepage, https://vistralis.org/vernier
Project-URL: Documentation, https://vistralis.org/vernier/docs
Project-URL: Source, https://github.com/vistralis/vernier
Project-URL: Issues, https://github.com/vistralis/vernier/issues
Project-URL: Changelog, https://github.com/vistralis/vernier/blob/main/CHANGELOG.md
Requires-Python: >=3.10
Requires-Dist: numpy>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Description-Content-Type: text/markdown

# vernier

![CI](https://github.com/vistralis/vernier/actions/workflows/ci.yml/badge.svg)
[![PyPI](https://img.shields.io/pypi/v/pyvernier)](https://pypi.org/project/pyvernier/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)

**Geometric calibration, fast.** Multi-camera intrinsics, multi-sensor
extrinsics, and bundle adjustment through a clean Python API on top of a
Ceres Solver / Sophus core.

```bash
pip install pyvernier
```

```python
import numpy as np
import vernier

K = np.array([[1400, 0, 960], [0, 1400, 540], [0, 0, 1]], dtype=np.float64)
distortion = np.zeros(5)

estimator = vernier.Estimator(loss="huber", loss_scale=1.0)
camera = estimator.add_camera("brown_conrady", K, distortion, fixed=["k3"])
frame  = estimator.add_frame(vernier.SE3(), vernier.Submanifold.SE3)

for point_3d, pixel_2d in observations:
    estimator.add_observation(camera, frame, point_3d, pixel_2d)

result = estimator.solve(max_iterations=200)
# SolveResult(converged=True, rms=0.31, iterations=8, time=0.076)

K_opt    = estimator.get_camera_matrix(camera)
dist_opt = estimator.get_dist_coefficients(camera)
pose_opt = estimator.get_frame_pose(frame)
```

## Features

### Projection Models

| Model | Distortion vector | Use case |
|-------|-------------------|----------|
| `BrownConrady` | `[k1, k2, p1, p2, k3]` | Standard rectilinear cameras (OpenCV default) |
| `KannalaBrandt` | `[k1, k2, k3, k4]` | Fisheye / wide-angle |
| `Division` | `[k1, k2]` | Lightweight radial distortion |
| `Rational` | `[k1..k6, p1, p2]` | Complex optics |

```python
# String API
camera = estimator.add_camera("kannala_brandt", K, dist)

# Class API
camera = estimator.add_camera(vernier.KannalaBrandt, K, dist)
```

### Per-Parameter Fixing

Hold any subset of intrinsic parameters constant during optimization:

```python
# Fix k3 by name
camera = estimator.add_camera("brown_conrady", K, dist, fixed=["k3"])

# Fix by enum
camera = estimator.add_camera(
    vernier.BrownConrady, K, dist,
    fixed=[vernier.BrownConrady.Param.K3],
)

# Optimize distortion only
camera = estimator.add_camera(
    "brown_conrady", K, dist,
    fixed=["fx", "fy", "cx", "cy"],
)
```

### Submanifold Pose Constraints

Choose which degrees of freedom of an SE(3) frame are optimized:

```python
# Full 6-DOF (default)
frame = estimator.add_frame(pose, vernier.Submanifold.SE3)

# Translation only
frame = estimator.add_frame(pose, vernier.Submanifold.TRANSLATION)

# Rotation only
frame = estimator.add_frame(pose, vernier.Submanifold.ROTATION)

# Arbitrary axis combination
frame = estimator.add_frame(
    pose,
    vernier.Submanifold.X | vernier.Submanifold.Y | vernier.Submanifold.WZ,
)

# Held fixed
frame = estimator.add_frame(pose, vernier.Submanifold.NONE)
```

### Robust Loss Functions

Per-observation loss for outlier handling. Default is **Huber (1.0 px)** —
the standard in bundle adjustment.

| Loss | Behavior | When to use |
|------|----------|-------------|
| `"none"` | Pure L2 | Clean synthetic data |
| `"huber"` | L2 below scale, L1 above | **Default** — calibration with mild outliers |
| `"cauchy"` | Aggressive downweighting | Noisy real-world data |
| `"soft_l1"` | Smooth L1 approximation | Dense correspondences |

```python
# Estimator-wide default
estimator = vernier.Estimator(loss="cauchy", loss_scale=0.5)

# Per-observation override
estimator.add_observation(camera, frame, p3d, p2d, loss="cauchy", loss_scale=0.5)
```

## Architecture

```
src/vernier/
├── __init__.py        Public re-exports + version
├── _core.py           Lie groups, projection models, enums, results
└── estimator.py       Estimator — Pythonic problem-construction API
```

The optimization core wraps [Ceres Solver](http://ceres-solver.org/) for
non-linear least squares with autodiff and [Sophus](https://github.com/strasdat/Sophus)
for SE(3) / SO(3) manifold operations. Projection models are stateless C++
trait structs dispatched via `std::variant` + `std::visit` so Ceres gets
exact Jacobian block sizes per camera.

## Documentation

Full documentation will be available at
[vistralis.org/vernier](https://vistralis.org/vernier).

## Development

```bash
pip install -e ".[dev]"
ruff check .
ruff format --check .
pytest
```

## License

Apache-2.0 — see [LICENSE](LICENSE) for details.

Copyright (c) 2026 Vistralis Labs.
