locus-tag
Locus is a high-performance fiducial marker detector (AprilTag & ArUco) written in Rust with zero-copy Python bindings. Designed for robotics and autonomous systems, it aims to balance low latency, high recall, and sub-pixel precision.
[!WARNING] Experimental Status: Locus is currently an experimental project. The API is subject to breaking changes. While performance exceeds alternatives on ICRA2020, it is not recommended for production systems. Photo-realistic benchmarks are being developed under render-tag.
Key Features
- High-Performance Core: Written in Rust (2024 Edition) with a focus on Data-Oriented Design.
- Encapsulated Facade: Simple, ergonomic
DetectorAPI that manages complex memory lifetimes (arenas, SoA batches) internally. - Runtime SIMD Dispatch: Automatically utilizes AVX2, AVX-512, or NEON based on host CPU capabilities.
- Vectorized Python API: Returns detection results as a single
DetectionBatchobject containing parallel NumPy arrays for maximum throughput. - GIL-Free Execution: Releases the Python Global Interpreter Lock (GIL) during detection to enable true multi-threaded applications.
- Memory Efficient: Uses
bumpaloarena allocation to achieve zero heap allocations in the detection hot-path. - Advanced Pose Estimation: High-precision 6-DOF recovery using IPPE-Square or weighted Levenberg-Marquardt with corner uncertainty modeling.
- Visual Debugging: Native integration with the Rerun SDK for real-time pipeline inspection.
Performance
Locus ships three scenario-specific SOTA presets alongside the production default. Choose the preset that matches your use case:
| Scenario | Preset | Key metric |
|---|---|---|
| Dense multi-tag detection | sota_pure_tags_default() |
96.2% recall (ICRA forward, 50 images) |
| Touching-tag checkerboard grids | sota_checkerboard_default() |
91.4% recall (ICRA checkerboard, 50 images) |
| Single-tag metrology / calibration | sota_metrology_default() |
0.16–0.29 px corner RMSE (hub, 4 resolutions) |
| Balanced production | production_default() |
100% recall + precision on fixtures |
ICRA 2020 — Multi-Tag Detection (50 images, forward/pure_tags)
| Detector | Recall | RMSE |
|---|---|---|
Locus (sota_pure_tags_default) |
96.2% | 0.315 px |
Locus (production_default) |
76.9% | 0.274 px |
| AprilTag 3 | 62.3% | 0.22 px |
| OpenCV | 33.2% | 0.92 px |
ICRA 2020 — Checkerboard (50 images, forward/checkerboard_corners)
| Detector | Recall | RMSE |
|---|---|---|
Locus (sota_checkerboard_default) |
91.4% | 0.458 px |
| Locus (legacy checkerboard) | 73.0% | 0.332 px |
Locus (production_default) |
— | — |
Production without 4-connectivity merges adjacent tag regions; not applicable to checkerboard scenes.
Hub — Single-Tag Metrology (720p, PoseEstimationMode::Accurate)
| Config | Recall | Corner RMSE | Trans P50 | Rot P50 |
|---|---|---|---|---|
Locus (sota_metrology_default) |
96.0% | 0.277 px | 1.0 mm | 0.35° |
Locus (production_default) |
100% | 0.933 px | 5.8 mm | 2.20° |
sota_metrology_default uses the EdLines Joint Gauss-Newton solver — per-corner 2×2 covariances propagate directly to the weighted LM pose solver, achieving 3–6× lower corner RMSE and 6–8× lower pose error vs production.
Full benchmark tables, methodology, and per-resolution results: docs/engineering/benchmarking/sota_metrology_20260321.md
Quick Start
Installation
pip install locus-tag
For development, build from source using uv:
git clone https://github.com/NoeFontana/locus-tag
cd locus-tag
uv run maturin develop -r
Basic Usage
The simplest way to detect tags using default settings:
import cv2
import locus
# Load image in grayscale
img = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE)
# Create detector and detect tags (defaults to AprilTag 36h11)
detector = locus.Detector()
batch = detector.detect(img)
# batch is a vectorized DetectionBatch object
for i in range(len(batch)):
print(f"ID: {batch.ids[i]}, Center: {batch.centers[i]}")
Advanced Configuration
Use semantic keyword arguments for fine-grained control and performance tuning:
from locus import Detector, TagFamily, DecodeMode
# Configure for maximum recall on small, blurry tags
detector = Detector(
decode_mode=DecodeMode.Soft,
upscale_factor=2,
families=[TagFamily.AprilTag36h11, TagFamily.ArUco4x4_50]
)
batch = detector.detect(img)
3D Pose Estimation
Recover the 6-DOF transformation between the camera and the tag:
from locus import CameraIntrinsics, PoseEstimationMode
# Camera parameters (fx, fy, cx, cy)
intrinsics = CameraIntrinsics(fx=800.0, fy=800.0, cx=640.0, cy=360.0)
# Pass intrinsics and physical tag size (meters)
batch = detector.detect(
img,
intrinsics=intrinsics,
tag_size=0.10,
pose_estimation_mode=PoseEstimationMode.Accurate
)
if batch.poses is not None:
# batch.poses is (N, 7) array: [tx, ty, tz, qx, qy, qz, qw]
print(f"First tag translation: {batch.poses[0, :3]}")
print(f"First tag quaternion: {batch.poses[0, 3:]}")
Visual Debugging with Rerun
Locus provides a powerful visualization tool to inspect every stage of the pipeline (thresholding, segmentation, quad candidates, bit grids).
# Run the visualizer on a dataset using the dev/bench dependency groups
uv run --group dev --group bench tools/cli.py visualize --scenario forward --limit 5
Development & Benchmarking
Locus includes a rigorous suite to ensure detection quality and latency targets.
# Prepare local datasets
uv run --group dev --group bench tools/cli.py bench prepare
# Run full evaluation suite and compare with competitors
uv run --group dev --group bench tools/cli.py bench real --compare
Detailed documentation for profiling, architecture, and coordinate systems is available in the Docs Site.
License
Dual-licensed under Apache 2.0 or MIT.