Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

whirl-tf

License Crates.io Docs PyPI

whirl-tf provides timestamped coordinate-frame transforms for robotics. The core is implemented in Rust and exposed as both the whirl_tf crate and a Python package built with PyO3 and maturin.

The transform buffer supports static and dynamic edges, interpolation between dynamic samples, explicit-timestamp lookup, matrix conversion, frame-name validation, and a small set of canonical robot frame constants. It does not extrapolate outside the available dynamic history.

Installation

Install the Python package from PyPI:

pip install whirl-tf

Or add the Rust crate:

cargo add whirl_tf

Python 3.10 and newer are supported. Wheels are published for manylinux x86-64, manylinux AArch64, and macOS Apple silicon.

Python example

from whirl_tf import TransformBuffer

identity = [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0],
]

buffer = TransformBuffer(history_seconds=10.0)
buffer.insert_transform("field", "odom", identity, stamp_ns=0, is_static=True)
T_odom_to_field = buffer.lookup("odom", "field", stamp_ns=1_000_000_000)

lookup(from_frame, to_frame, stamp_ns) returns a 4 x 4 matrix that maps coordinates expressed in from_frame into to_frame.

ROS-style Python TransformStamped and TFMessage objects can be inserted with insert_transform_stamped and insert_transform_message; these methods use attribute access and therefore do not require a ROS Python dependency.

Filtering stamped messages

MessageFilter holds a stamped message until its transform is available. It reads message.header.frame_id and message.header.stamp by attribute, so it works with ROS messages without making whirl-tf depend on ROS:

from whirl_tf import MessageFilter, TransformBuffer

buffer = TransformBuffer(history_seconds=10.0)

def handle_scan(scan):
    matrix = buffer.lookup(
        scan.header.frame_id,
        "field",
        scan.header.stamp.sec * 1_000_000_000 + scan.header.stamp.nanosec,
    )
    # Process the scan with matrix here.

scan_filter = MessageFilter(buffer, "field", queue_size=100, callback=handle_scan)

# These can be used directly as subscription callbacks. Their execution order
# no longer matters: every successful transform insertion wakes the filter.
scan_subscription_callback = scan_filter.add
tf_subscription_callback = buffer.insert_transform_message

Waiting messages are delivered in FIFO order, including when a later message becomes transformable first. A nonzero queue size bounds memory and discards the oldest waiting message on overflow; register_failure_callback reports that message and a FilterFailureReason. A queue size of zero is unbounded.

Use set_target_frames when every message must be transformable into multiple frames. set_tolerance_ns additionally requires transform coverage at the message timestamp plus the given interval, matching tf2's tolerance behavior.

Rust example

use std::time::Duration;

use nalgebra::Isometry3;
use whirl_tf::{MessageFilter, TransformBuffer};

let mut buffer = TransformBuffer::new(Duration::from_secs(10));
buffer.insert_isometry("field", "odom", &Isometry3::identity(), 0, true)?;
let odom_to_field = buffer.lookup_ns("odom", "field", 1_000_000_000)?;

let mut filter = MessageFilter::new("field", 100)?;
filter.add("scan", "odom", 1_000_000_000);
let ready_scans = filter.drain_ready(&buffer);
assert_eq!(ready_scans, ["scan"]);

# Ok::<(), whirl_tf::TransformBufferError>(())

The crate's default ros feature also exposes minimal ROS 2 transform message types and conversion helpers. Disable default features when only the ROS-free timestamp and matrix API is needed:

whirl_tf = { version = "0.1", default-features = false }

Development

The repository contains a Rust core crate and a separate PyO3 binding crate. Pixi provides the development tasks:

pixi run rs-check
pixi run python-stubs
pixi run -e py py-lint
pixi run -e py py-build-wheel

The generated wheel is written to whirl_tf_py/dist/.

License

Licensed under either the Apache License, Version 2.0 or the MIT License, at your option.

Metadata

Release files for whirl-tf 0.1.0a2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for whirl-tf 0.1.0a2
File Interpreter ABI Platform
whirl_tf-0.1.0a2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
whirl_tf-0.1.0a2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
whirl_tf-0.1.0a2-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details

Total release size: 1.2 MB

Release files / whirl_tf-0.1.0a2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL whirl_tf-0.1.0a2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 439.3 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
77ff139dc158966e444430dbfb4c6cf15898f1e5879188673ec52809778befad
BLAKE2b-256 checksum
How to use checksums
59b48c135722eac8f95d2cbb4fbb6c62d3f9b169ce7b4632d6608c2e39b7d7cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / whirl_tf-0.1.0a2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL whirl_tf-0.1.0a2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 404.3 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
3f5eda2b19ca6527bbf33e0a1d6058f90dd23bd4f0c4bc0c36f6d72f9a2d4713
BLAKE2b-256 checksum
How to use checksums
5331e72111fb2e0c9df44027c5f73cae86d243402557dd599335530af21bbb54
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / whirl_tf-0.1.0a2-cp310-abi3-macosx_11_0_arm64.whl

Download URL whirl_tf-0.1.0a2-cp310-abi3-macosx_11_0_arm64.whl
Size 384.8 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
11b789e5597b2b60286317b43e2bf2200774831edc0d793a801dd4f7ab9aac98
BLAKE2b-256 checksum
How to use checksums
34e76ef55051bd6f1e782f0205f762557e63a46b63779f7874388e326192297f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page