Transforms
Transforms points from one reference-frame representation to another.
Install / Use
/learn @deniz-hofmeister/TransformsREADME
Transforms
A fast, middleware-independent coordinate transform library for Rust.
Overview
transforms is a pure Rust library for managing coordinate transformations between different reference frames. It is designed for robotics and computer vision applications where tracking spatial relationships between sensors, actuators, and world coordinates is essential.
Key characteristics:
- Middleware-independent: No ROS2, DDS, or any communication layer dependencies. Use it standalone or wrap it with your own pub-sub system. Checkout roslibrust_transforms if you are looking for a wrapped system.
no_stdcompatible: Works in embedded and resource-constrained environments.- Memory safe: Uses
#![forbid(unsafe_code)]throughout. - Inspired by tf2: Familiar concepts for robotics developers, but with a Rust-first API.
Features
- Transform Interpolation: Smooth interpolation between transforms at different timestamps using spherical linear interpolation (SLERP) for rotations and linear interpolation for translations.
- Transform Chaining: Automatic computation of transforms between indirectly connected frames by traversing the frame tree.
- Static Transforms: Transforms with the static timestamp value are treated as static (
t=0by default). - Time-based Buffer Management: Automatic cleanup of old transforms (with
stdfeature) or manual cleanup (forno_std). - O(log n) Lookups: Efficient transform retrieval using
BTreeMapstorage. - Transformable Trait: Implement on your own types to make them transformable between coordinate frames.
- Transform Into: Resolve and apply transforms directly from a
Localizedvalue withget_transform_for, eliminating manual frame and timestamp bookkeeping.
What's New
v1.4.0 — Read-only getters
get_transform, get_transform_for, and get_transform_at now take &self instead of &mut self, making concurrent reads possible without exclusive access.
// No &mut needed — share the registry freely
let registry: &Registry = /* ... */;
let tf = registry.get_transform("base", "sensor", timestamp)?;
v1.3.0 — get_transform_for and Localized trait
Resolve and apply a transform directly from any type that implements Localized, without manual frame/timestamp bookkeeping.
let point = Point { position: Vector3::new(1.0, 0.0, 0.0), orientation: Quaternion::identity(), timestamp, frame: "camera".into() };
let tf = registry.get_transform_for(&point, "map")?;
v1.2.0 — TimePoint trait and get_transform_at
All core types are now generic over time via the TimePoint trait. std::time::SystemTime works out of the box. A new get_transform_at API enables querying transforms at different timestamps per frame ("time travel").
// Use SystemTime instead of Timestamp
let mut registry = Registry::<SystemTime>::new(Duration::from_secs(60));
// Time travel: source at t1, target at t2, through a fixed frame
let tf = registry.get_transform_at("target", t2, "source", t1, "world")?;
v1.1.0 — Static/dynamic mixing fix
Fixed a bug where static transforms (timestamp = 0) and dynamic transforms could not coexist in the same tree. Buffer expiration now uses the latest inserted timestamp instead of wall-clock time.
// Static sensor mount + dynamic robot pose now work together
registry.add_transform(static_camera_mount); // timestamp = 0
registry.add_transform(dynamic_robot_pose); // timestamp = now
let tf = registry.get_transform("map", "camera", Timestamp::now())?;
v1.0.0 — Stable release
First stable release with no_std support, transform chaining, SLERP interpolation, Transformable trait, and automatic buffer cleanup.
let mut registry = Registry::new(Duration::from_secs(60));
registry.add_transform(transform);
let result = registry.get_transform("base", "sensor", timestamp)?;
Installation
Add to your Cargo.toml:
[dependencies]
transforms = "1.4.1"
Feature Flags
| Feature | Default | Description |
|---------|---------|-------------|
| std | Yes | Enables automatic buffer cleanup and Timestamp::now() |
For no_std environments:
[dependencies]
transforms = { version = "1.4.1", default-features = false }
Quick Start
use core::time::Duration;
use transforms::{
geometry::{Quaternion, Transform, Vector3},
time::Timestamp,
Registry,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a registry with 60-second transform buffer
let mut registry = Registry::new(Duration::from_secs(60));
let timestamp = Timestamp::now();
// Define a transform: sensor is 1 meter along X-axis from base
let transform = Transform {
translation: Vector3::new(1.0, 0.0, 0.0),
rotation: Quaternion::identity(),
timestamp,
parent: "base".into(),
child: "sensor".into(),
};
// Add and retrieve the transform
registry.add_transform(transform);
let result = registry.get_transform("base", "sensor", timestamp)?;
println!("Transform: {:?}", result);
Ok(())
}
API Reference
Registry
// std feature
pub fn new(max_age: Duration) -> Self
// no_std
pub fn new() -> Self
pub fn add_transform(&mut self, transform: Transform<T>)
pub fn get_transform(&mut self, from: &str, to: &str, timestamp: T) -> Result<Transform<T>, TransformError>
pub fn get_transform_for<U: Localized<T>>(&mut self, value: &U, target_frame: &str) -> Result<Transform<T>, TransformError>
pub fn delete_transforms_before(&mut self, timestamp: T)
Core Types
| Type | Description |
|------|-------------|
| Transform<T = Timestamp> | Rigid body transformation (translation + rotation + timestamp + frames) |
| Vector3 | 3D vector with x, y, z components (f64) |
| Quaternion | Unit quaternion for rotations with w, x, y, z components (f64) |
| Timestamp | Time representation in nanoseconds (u128) |
| TimePoint | Trait for custom timestamp types used by Transform, Buffer, and Registry |
| Point | Example transformable type with position, orientation, timestamp, frame |
For complete API documentation, see docs.rs/transforms.
Architecture
The library is organized around three core components:
┌─────────────────────────────────────────────────────────┐
│ Registry │
│ ┌─────────────────────────────────────────────────┐ │
│ │ HashMap<child_frame, Buffer> │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Buffer "b" │ │ Buffer "c" │ ... │ │
│ │ │ parent: "a" │ │ parent: "b" │ │ │
│ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ │
│ │ │ │Transform│ │ │ │Transform│ │ │ │
│ │ │ │ @ t=0 │ │ │ │ @ t=1 │ │ │ │
│ │ │ │Transform│ │ │ │Transform│ │ │ │
│ │ │ │ @ t=1 │ │ │ │ @ t=2 │ │ │ │
│ │ │ └─────────┘ │ │ └─────────┘ │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Registry
The main interface for managing transforms. It stores Buffer instances (one per child frame) and handles:
- Adding new transforms
- Retrieving transforms between any two frames (with automatic chaining)
- Traversing the frame tree to compute indirect transforms
- Automatic cleanup of expired transforms (with
stdfeature)
Buffer
Time-indexed storage for transforms between a specific child-parent frame pair. Uses a BTreeMap<T, Transform<T>> for O(log n) lookups with automatic interpolation for timestamps between stored values.
Transform
The core data structure representing a rigid body transformation:
pub struct Transform<T = Timestamp>
where
T: TimePoint,
{
pub translation: Vector3, // Position offset (x, y, z)
pub rotation: Quaternion, // Orientation (w, x, y, z)
pub timestamp: T, // When this transform is valid
pub parent: String, // Destination frame
pub child: String, // Source frame
}
Localized and Transformable Traits
Implement Transformable on your own types to make them transformable, and Localized to enable automatic transform lookup via get_transform_for:
pub trait Localized<T = Timestamp>
where
T: TimePoint,
{
fn frame(&self) -> &str;
fn timestamp(&self) -> T;
}
pub trait Transformable<T = Timestamp>
where
T: TimePoint,
{
fn transform(&mut self, transform: &Transform<T>) -> Result<(), TransformError>;
}
The Localized trait provides frame and timestamp introspection, while Transformable handles applying transforms. They are separate so that pure geometry types can implement Transformable without needing frame/timestamp metadata. The library provides a Point type as a reference implementation o
