Metadata-Version: 2.4
Name: telekinesis-axon
Version: 0.2.1
Summary: Camera calibration package within the Telekinesis ecosystem.
Author-Email: Telekinesis <support@telekinesis.ai>
License-Expression: LicenseRef-Proprietary
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Project-URL: Homepage, https://telekinesis.ai
Project-URL: Documentation, https://docs.telekinesis.ai
Project-URL: Telekinesis Examples Repository, https://github.com/telekinesis-ai/telekinesis-examples
Requires-Python: <3.13,>=3.10
Requires-Dist: loguru
Requires-Dist: numpy>=2.0
Requires-Dist: opencv-python
Requires-Dist: pyyaml
Requires-Dist: telekinesis-tf>=0.1.1
Provides-Extra: dev
Requires-Dist: ruff==0.15.8; extra == "dev"
Requires-Dist: pylint==4.0.5; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: ninja>=1.5; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Provides-Extra: dev-macos
Requires-Dist: delocate; extra == "dev-macos"
Provides-Extra: dev-linux
Requires-Dist: auditwheel; extra == "dev-linux"
Provides-Extra: dev-windows
Requires-Dist: delvewheel; extra == "dev-windows"
Provides-Extra: docs
Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
Requires-Dist: mkdocstrings[python]<1,>=0.25; extra == "docs"
Requires-Dist: mkdocs-awesome-pages-plugin<3,>=2; extra == "docs"
Description-Content-Type: text/markdown

<div align="center">
  <p>
    <a align="center" href="" target="_blank">
      <img
        width="100%"
        src="https://telekinesis-public-assets.s3.us-east-1.amazonaws.com/Telekinesis+Banner.png"
      >
    </a>
  </p>

  <br>

[Telekinesis Examples](https://github.com/telekinesis-ai/telekinesis-examples) | [Documentation](https://docs.telekinesis.ai)
<br>

[![PyPI version](https://img.shields.io/pypi/v/telekinesis-axon)](https://pypi.org/project/telekinesis-axon/)
[![License](https://img.shields.io/pypi/l/telekinesis-axon)](https://pypi.org/project/telekinesis-axon/)
[![Python versions](https://img.shields.io/pypi/pyversions/telekinesis-axon)](https://pypi.org/project/telekinesis-axon/)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-blue)](https://pypi.org/project/telekinesis-axon/)

</div>

<p align="center">
  <a href="https://github.com/telekinesis-ai">GitHub</a>
  &nbsp;•&nbsp;
  <a href="https://www.linkedin.com/company/telekinesis-ai/">LinkedIn</a>
  &nbsp;•&nbsp;
  <a href="https://x.com/telekinesis_ai">X</a>
  &nbsp;•&nbsp;
  <a href="https://discord.gg/7NnQ3bQHqm">Discord</a>
</p>

# Telekinesis Axon

**Telekinesis Axon** is a camera calibration library for the Telekinesis ecosystem, with a C++ core (targets, calibration, geometry, quality) exposed to Python via pybind11. It supports intrinsic calibration, eye-in-hand (hand-eye) extrinsic calibration, multi-camera extrinsic calibration, and benchmarking, built on OpenCV.

The hardware-driving `runtime` layer (`DataCollector`, `PerturbationSampler`) stays pure Python — camera/robot SDKs are inherently async/IO-bound and don't benefit from a native rewrite.

## Installation

```bash
pip install telekinesis-axon
```

Ships as a prebuilt wheel (Linux/Windows, Python 3.11/3.12) from Telekinesis's private package registry — no C++ toolchain needed. If you're building from source instead (e.g. contributing to axon itself), see [DEVELOPMENT.md](DEVELOPMENT.md).

## Targets

Define your calibration board once and pass it to any calibrator:

```python
from telekinesis.axon.targets import ChessboardTarget, CharucoTarget, ArucoTarget

chessboard = ChessboardTarget(size=(9, 6), square_size=0.025)  # meters

charuco = CharucoTarget(
    squares_x=6,
    squares_y=9,
    square_length=0.012,
    marker_length=0.009,
    aruco_dict_id="DICT_4X4_1000",  # or the raw cv2.aruco.DICT_4X4_1000 int
)

aruco = ArucoTarget(
    board_size=(5, 7),
    marker_length=0.025,
    marker_separation=0.005,
)
```

## Intrinsic Calibration

A single `IntrinsicCalibrator` dispatches on the target type you pass it — no more separate `ArucoIntrinsicCalibrator` / `CharucoIntrinsicCalibrator` subclasses or an `IntrinsicBackend` indirection:

```python
from telekinesis.axon import IntrinsicCalibrator, IntrinsicOptions
from telekinesis.axon.targets import CharucoTarget

target = CharucoTarget(squares_x=6, squares_y=9, square_length=0.012, marker_length=0.009)

options = IntrinsicOptions()
options.per_view_error_threshold = 0.8

calibrator = IntrinsicCalibrator(target, options)
result = calibrator.calibrate(images)

result.ok                     # bool
result.intrinsic_matrix       # (3, 3) np.ndarray
result.distortion_coefficients
result.reprojection_error
result.successful_indices
result.low_view_error_indices
```

## Eye-in-Hand Calibration

Calibrate the transform between the camera and the robot TCP. Requires images of a calibration board alongside the robot's TCP pose (`robot_T_tcp` as 4×4 homogeneous matrices) for each frame.

```python
from telekinesis.axon import EyeInHandCalibrator
from telekinesis.axon.targets import CharucoTarget

target = CharucoTarget(squares_x=6, squares_y=9, square_length=0.012, marker_length=0.009)

calibrator = EyeInHandCalibrator(target)

result = calibrator.calibrate(
    robot_T_tcp_list=robot_poses,  # list of (4, 4) np.ndarray
    image_list=images,
    method="TSAI",
)

result.ok               # bool
result.tcp_T_camera     # (4, 4) np.ndarray
result.intrinsic_matrix
result.distortion_coefficients
result.reprojection_error
result.consistency       # TargetConsistencyStats, when result.has_consistency
```

### Pass pre-computed per-frame poses

If you have `camera_T_target` transforms from an external source, pass them via `camera_T_target_list` to skip `EyeInHandCalibrator`'s internal `solvePnP`. The caller is responsible for aligning `robot_T_tcp_list` and `image_list` to it.

```python
result = calibrator.calibrate(
    robot_T_tcp_list=robot_poses,
    image_list=images,
    camera_T_target_list=external_camera_T_target_list,
    intrinsic_matrix=K,
    distortion_coefficients=d,
)
```

## Multi-Camera Calibration

Calibrate N ≥ 2 synchronized cameras against a reference camera via pairwise `cv::stereoCalibrate`, with an automatic triangulation validation pass:

```python
from telekinesis.axon import MultiCameraCalibrator
from telekinesis.axon.targets import CharucoTarget

target = CharucoTarget(squares_x=6, squares_y=9, square_length=0.012, marker_length=0.009)

calibrator = MultiCameraCalibrator(target, num_cameras=3, reference_index=1)
result = calibrator.calibrate(image_lists)  # image_lists[cam][frame]

result.ok                          # True iff every non-reference pair solved
result.reference_T_camera_list     # per-camera (4, 4) np.ndarray
result.intrinsic_matrices
result.pair_reprojection_errors
```

## Benchmarking

Cross-validate a target/`IntrinsicOptions` configuration and, optionally, a hand-eye round trip:

```python
from telekinesis.axon import CalibrationBenchmark, IntrinsicOptions
from telekinesis.axon.targets import CharucoTarget

target = CharucoTarget(squares_x=6, squares_y=9, square_length=0.012, marker_length=0.009)

bench = CalibrationBenchmark()
result = bench.run(
    images=my_images,
    target=target,
    options=IntrinsicOptions(),
    robot_poses=my_robot_poses,             # optional — enables the round-trip metric
    eye_in_hand_options=IntrinsicOptions(), # optional
    n_splits=5,
)
CalibrationBenchmark.save_json([result], "calibration_benchmark.json")
print(CalibrationBenchmark.format_results([result]))
```

**Output (`calibration_benchmark.json`):**

```json
{
  "run_timestamp": "2026-05-18T...",
  "results": [
    {
      "backend_name": "OpenCV",
      "reprojection_error": 0.42,
      "held_out_reprojection_error": 0.61,
      "hand_eye_roundtrip_error": 1.3,
      "camera_matrix": [[fx, 0, cx], [0, fy, cy], [0, 0, 1]],
      "dist_coeffs": [...]
    }
  ]
}
```

## Resources

- **Examples** — [Telekinesis Examples](https://github.com/telekinesis-ai/telekinesis-examples)
- **Documentation** — [Telekinesis Documentation](https://docs.telekinesis.ai)

## Support

- Open an [issue](https://github.com/telekinesis-ai/telekinesis-examples/issues) on GitHub
- Contact the team at support@telekinesis.ai or on [Discord](https://discord.com/invite/7NnQ3bQHqm)

## License

Proprietary — © Telekinesis. All rights reserved.
