{"title":"TransformFrame Transform System","description":"High-performance coordinate frame management for real-time robotics","slug":"concepts/transform-frame","content":"# TransformFrame Transform System\n\nTransformFrame is HORUS's coordinate frame management system — a real-time-safe replacement for ROS2 TF2. It manages the spatial relationships between coordinate frames (e.g., \"where is the camera relative to the robot base?\") with lock-free lookups and sub-microsecond performance.\n\n> **Rust**: `TransformFrame` lives in the `horus_tf` crate — `use horus_tf::prelude::*;`\n\n## Why TransformFrame?\n\n| Problem with TF2 | TransformFrame Solution |\n|------------------|-----------------|\n| Mutex-based locking | Lock-free seqlock protocol |\n| Unbounded latency spikes | Predictable sub-microsecond latency |\n| String-only frame lookup | Dual API: Integer IDs + Names |\n| No hard real-time support | Real-time safe, no allocations in hot path |\n\n### Performance\n\n| Operation | TransformFrame | ROS2 TF2 | Speedup |\n|-----------|--------|----------|---------|\n| Lookup by ID | ~50ns | N/A | - |\n| Lookup by name | ~200ns | ~2us | 10x |\n| Chain resolution (depth 3) | ~150ns | ~5us | 33x |\n| Chain resolution (depth 10) | ~2.5us | ~15us | 6x |\n| Concurrent reads (4 threads) | ~800ns | ~8us | 10x |\n\n---\n\n## Basic Usage\n\n\n## Transform Type\n\n```rust\n// simplified\npub struct Transform {\n    pub translation: [f64; 3],  // [x, y, z] in meters\n    pub rotation: [f64; 4],     // quaternion [x, y, z, w] (Hamilton convention)\n}\n```\n\n### Creating Transforms\n\n\n### Transform Operations\n\n\n---\n\n## Frame Registration\n\n### Dynamic Frames\n\nDynamic frames have transforms that change over time (e.g., robot joints, camera tracking):\n\n\n### Static Frames\n\nStatic frames have transforms that never change (e.g., sensor mounts, fixed offsets):\n\n\nStatic frames use less memory since they don't maintain a history buffer.\n\n### Frame Queries\n\n\n---\n\n## Transform Lookups\n\n### By Name\n\n\n### By ID (Hot Path)\n\nFor control loops running at 1kHz+, cache frame IDs and use the ID-based API:\n\n\n### Time-Travel Queries\n\nTransformFrame maintains a history buffer of past transforms, enabling queries at past timestamps with interpolation:\n\n\nIf the exact timestamp isn't available, TransformFrame interpolates between the two nearest samples:\n- **Translation**: Linear interpolation\n- **Rotation**: SLERP (Spherical Linear Interpolation)\n\n---\n\n## Configuration\n\n### Presets\n\n\n| Preset | Frames | Static | History | Cache | Memory |\n|--------|--------|--------|---------|-------|--------|\n| `small()` | 256 | 128 | 32 | 64 | ~550KB |\n| `medium()` | 1024 | 512 | 32 | 128 | ~2.2MB |\n| `large()` | 4096 | 2048 | 32 | 256 | ~9MB |\n\nAdditional presets for simulation:\n\n\n### Custom Configuration\n\n\n---\n\n## CLI Tools\n\nHORUS provides CLI equivalents of ROS2 tf2 tools:\n\n```bash\n# List all coordinate frames\nhorus tf list\nhorus tf list --json     # JSON output\n\n# Echo transform between frames (like ros2 run tf2_ros tf2_echo)\nhorus tf echo camera base_link\nhorus tf echo camera world --rate 10  # 10 Hz\n\n# Show frame tree (like ros2 run tf2_tools view_frames)\nhorus tf tree\n\n# Get detailed info about a specific frame\nhorus tf info camera\n\n# Check if transform exists between two frames\nhorus tf can camera world\n\n# Monitor transform update rates\nhorus tf hz\n```\n\n---\n\n## Statistics and Inspection\n\n### TransformFrameStats\n\nGet system-wide statistics via `tf.stats()`:\n\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `total_frames` | `usize` | Total registered frames |\n| `static_frames` | `usize` | Frames that never change |\n| `dynamic_frames` | `usize` | Frames updated at runtime |\n| `max_frames` | `usize` | Maximum capacity |\n| `history_len` | `usize` | Transform history buffer size |\n| `tree_depth` | `usize` | Maximum depth of the frame tree |\n| `root_count` | `usize` | Number of root frames (no parent) |\n\n### FrameInfo\n\nGet metadata for a specific frame via `tf.frame_info(name)`:\n\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | `String` | Frame name |\n| `id` | `FrameId` | Internal frame ID |\n| `parent` | `Option` | Parent frame name (`None` for root) |\n| `is_static` | `bool` | Whether this frame never changes |\n\n### Diagnostics\n\n```rust\n// simplified\n// Validate frame tree integrity\nhf.validate()?;\n```\n\n---\n\n## Design Decisions\n\n### Why Lock-Free Seqlock Instead of Mutex\n\nCoordinate frame lookups happen inside control loops running at 1kHz or faster. A mutex-based design (as in ROS2 TF2) means a writer updating a transform can block readers — and in a real-time system, priority inversion from mutex contention causes unbounded latency spikes. TransformFrame uses a seqlock protocol: writers increment a sequence number before and after updating, and readers retry if they detect a concurrent write. This guarantees that readers never block, even under heavy write load. The worst case for a reader is a retry (~50ns extra), not an unbounded wait.\n\n### Why Dual API (Integer IDs + String Names)\n\nString-based frame lookups require hashing and comparison on every call — acceptable for setup code but wasteful in a 1kHz control loop that looks up the same frames every tick. TransformFrame provides both: string-based registration (`register_frame(\"camera\", ...)`) for clarity at startup, and integer ID-based lookups (`tf_by_id(camera_id, world_id)`) for the hot path. Users cache frame IDs once during initialization and use them in the loop body, cutting lookup time from ~200ns to ~50ns. The string API remains available for introspection, CLI tools, and non-performance-critical code.\n\n### Why a Dedicated Crate Instead of a Separate Process\n\nIn ROS2, TF2 commonly involves separate broadcaster/listener nodes and launch wiring. TransformFrame lives in the `horus-tf` crate and is imported with `use horus_tf::prelude::*`; it is a library API, not a separate process you must remember to start. The CLI (`horus tf list`, `horus tf echo`) is the runtime introspection layer.\n\n## Trade-offs\n\n| Area | Benefit | Cost |\n|------|---------|------|\n| **Seqlock protocol** | Readers never block; predictable sub-microsecond latency under contention | Readers may retry during a concurrent write — rare but adds ~50ns when it happens |\n| **Integer ID API** | ~50ns lookups in hot paths — 4x faster than string-based | Users must cache IDs at startup; using IDs without caching defeats the purpose |\n| **History buffer** | Time-travel queries with interpolation for sensor fusion and replay | Fixed-size ring buffer per dynamic frame (default 32 entries) consumes memory even if time-travel is unused |\n| **Pre-allocated frame slots** | No heap allocation during runtime — real-time safe after initialization | Maximum frame count must be chosen at startup; exceeding it requires `enable_overflow` (which uses a HashMap and is not RT-safe) |\n| **Library API in `horus-tf`** | No separate transform process; explicit dependency boundary | Projects that need transforms add the `horus-tf` crate/import |\n| **Static frame optimization** | Static frames skip the history buffer — less memory, faster lookups | Changing a static frame (e.g., recalibration) requires an explicit `set_static_transform` call instead of the normal `update_transform` path |\n\n## See Also\n\n- **[Message Types](/concepts/message-types)** — TransformStamped and other spatial messages\n- **[Architecture](/concepts/architecture)** — How TransformFrame fits into HORUS\n- **[Quick Start](/getting-started/quick-start)** — Get started with HORUS","headings":["TransformFrame Transform System","Why TransformFrame?","Performance","Basic Usage","Transform Type","Creating Transforms","Transform Operations","Frame Registration","Dynamic Frames","Static Frames","Frame Queries","Transform Lookups","By Name","By ID (Hot Path)","Time-Travel Queries","Configuration","Presets","Custom Configuration","CLI Tools","List all coordinate frames","Echo transform between frames (like ros2 run tf2ros tf2echo)","Show frame tree (like ros2 run tf2tools viewframes)","Get detailed info about a specific frame","Check if transform exists between two frames","Monitor transform update rates","Statistics and Inspection","TransformFrameStats","FrameInfo","Diagnostics","Design Decisions","Why Lock-Free Seqlock Instead of Mutex","Why Dual API (Integer IDs + String Names)","Why a Dedicated Crate Instead of a Separate Process","Trade-offs","See Also"],"wordCount":1144}