affine transformations with units support
Project description
kaffine
kaffine
Kaffine is inspired by pint and affine. nd aims to make the creation of unit-aware affine transformations as easy as possible.
kaffine simplifies constructing complex 4x4 affine transformation matrices. It r offers native integration with Pint for physical units (mixing microns, millimeters, and inches), and bridges seamlessly with Scipy for advanced usage.
Designed for Computer Vision, Microscopy, and Robotics applications where "Sample-to-Camera" or "Stage-to-World" coordinates are critical.
🚀 Features
- Fluent Interface: Chain operations like
.translate_x(10).rotate_z(90). - Unit Aware: Native support for Pint. Move 100
micronson X and 2mmon Y without manual conversion. - Hardware Ready: Includes
decompose()to extract pixel size, rotation, and stage position from a raw matrix. - Type Safe: Built with modern Python 3.11+ type hints (
typing.Self,|unions). - Scipy Bridge: Cast to/from
scipy.spatial.transform.Rotationfor quaternions and slerp. - Zero-Crash Defaults: Scipy and Pint are optional dependencies. The library falls back to pure NumPy if they aren't installed.
📦 Installation
uv add kaffine
⚡ Quick Start
1. The Simple Math Interface
Perfect for standard graphics or geometry.
from kaffine import Affine
# Create a transform: Move 10 units X, then Rotate 45 deg Z
# (Note: Order is intuitive "Operation 1 -> Operation 2")
tf = Affine.new().translate_x(10).rotate_z(45)
# Apply to a point
point = [0, 0, 0]
result = tf * point
print(result)
# Output (4D homogeneous): [10.0, 0.0, 0.0, 1.0]
2. The "Hardware" Interface (with Units)
Perfect for controlling stages, cameras, or mixing scales.
from kaffine import Affine
import pint
ureg = pint.UnitRegistry()
# Define your setup's units
ureg.define("step = 0.1 um") # Piezo stepper resolution
ureg.define("pixel = 3.45 um") # Camera sensor pixel size
# Construct a Matrix that converts Pixels to Stage Coordinates (mm)
# Scenario:
# 1. Scale pixels to physical size
# 2. Rotate camera 90 degrees
# 3. Translate to stage position (defined in steps)
tf = Affine.new(base_unit="um", registry=ureg) \
.scale_uniform("1 pixel") \
.rotate_z(90) \
.translate_x("50000 step")
# Transform a point from Pixel Space to Millimeters
pixel_point = [100, 100, 0] # 100x100 px on image
world_point = tf.apply(pixel_point)
print(world_point)
# Output: <Quantity([5.0 -0.345 0. 1.], 'millimeter')>
📚 Advanced Usage
Matrix Decomposition (Calibration)
If you have a calibration matrix and need to know the physical properties of your setup (e.g., "What is my effective pixel size?"), use .decompose().
# Assume 'tf' is a calibrated matrix loaded from a file
info = tf.decompose()
print(f"Position: {info.translation}") # e.g., [10.5, 2.0, 0] mm
print(f"Pixel Size: {info.scale}") # e.g., [0.00345, 0.00345, 1.0]
print(f"Rotation: {info.rotation}") # e.g., [0, 0, 1.5] degree
Scipy Interoperability
Need Quaternions or Spherical Interpolation?
tf = Affine.new().rotate_x(45).rotate_y(30)
# Cast to Scipy
r_scipy = tf.to_scipy_rotation()
print(r_scipy.as_quat())
# ... do complex math ...
r_scipy_inverted = r_scipy.inv()
# Cast back
tf_inv = Affine.from_scipy(r_scipy_inverted)
🛠 API Reference
Affine.new(base_unit="mm", registry=None)
Starts a new transformation chain.
base_unit: The physical unit the internal matrix numbers represent.registry: Yourpint.UnitRegistry.
Builder Methods
All methods return a new Affine instance (immutable-style chaining).
.translate(x, y, z)/.translate_x(val)....rotate(angle, axis='z')/.rotate_x(angle)....scale(x, y, z)/.scale_uniform(s)
Application
tf * [x, y, z]: Returns a raw Numpy array (4D).tf * other_tf: Composes two transforms.tf.apply(points): Accepts/Returns Pint Quantities.
Utilities
tf.decompose(): ReturnsDecomposition(translation, scale, rotation).tf.to_scipy_rotation(): Returnsscipy.spatial.transform.Rotation.
🧪 Running Tests
The project includes a comprehensive pytest suite covering math accuracy, unit conversion, and edge cases.
uv run pytest tests/
📄 License
MIT License. Feel free to use this in your commercial or open-source projects.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kaffine-1.0.1.tar.gz.
File metadata
- Download URL: kaffine-1.0.1.tar.gz
- Upload date:
- Size: 7.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2bd3a173c76c7a3d32911be1698470e7225ad2b47c067c00008fd470e356834
|
|
| MD5 |
c070e2e098d234b35974fc50854ee7c7
|
|
| BLAKE2b-256 |
dd62b6f9d3872f43e688713b53584660de7bed14a1e104d20285252a871c5d15
|
File details
Details for the file kaffine-1.0.1-py3-none-any.whl.
File metadata
- Download URL: kaffine-1.0.1-py3-none-any.whl
- Upload date:
- Size: 6.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed50b65d216fee4da2d1da7643920004ae9cfb5c0bb9602f1c01d345c3e7ee5b
|
|
| MD5 |
9c2c42404f356afc3c29f5add3d8fde8
|
|
| BLAKE2b-256 |
5a996080125029a78ab835138665abcde2f4b7d63ee826e0727ff8d85dd3c264
|