SkillAgentSearch skills...

Transforms

Transforms points from one reference-frame representation to another.

Install / Use

/learn @deniz-hofmeister/Transforms
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Transforms

Crates.io Documentation License: MIT tests unsafe forbidden no_std Downloads

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_std compatible: 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=0 by default).
  • Time-based Buffer Management: Automatic cleanup of old transforms (with std feature) or manual cleanup (for no_std).
  • O(log n) Lookups: Efficient transform retrieval using BTreeMap storage.
  • Transformable Trait: Implement on your own types to make them transformable between coordinate frames.
  • Transform Into: Resolve and apply transforms directly from a Localized value with get_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 std feature)

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

View on GitHub
GitHub Stars35
CategoryDevelopment
Updated12d ago
Forks3

Languages

Rust

Security Score

90/100

Audited on Mar 24, 2026

No findings