EfficientLoFTR Technical Reference¶
spatialhub.models.efficient_loftr provides an ONNX Runtime adapter for EfficientLoFTR, a semi-dense local feature matching model using sparse transformers.
Supported Model Variants¶
The EfficientLoFTRAdapter accepts two model precision variants via model_type:
Model Variant (model_type) |
ONNX File | Description | Target Use Case |
|---|---|---|---|
"full" (Default) |
eloftr_outdoor_full.onnx |
Full precision semi-dense feature matching model. | Maximum matching precision & robust keypoint coverage. |
"opt" |
eloftr_outdoor_opt.onnx |
Optimized/reparameterized lightweight model variant. | High throughput, lower memory footprint, and edge processing. |
Preprocessing & Post-processing Flow¶
EfficientLoFTR matches coarse-to-fine keypoints across image pairs without requiring PyTorch during inference.
graph TD
A["Image Pair (A, B): Paths / Arrays"] --> B["Aspect Ratio Downscaling (max_dim)"]
B --> C["Multiple-of-32 Dimension Alignment"]
C --> D["Grayscale Conversion & Normalization"]
D --> E["Bottom-Right Zero Padding to Shared (W_pad, H_pad)"]
E --> F["ONNX Runtime Forward Pass"]
F --> G["Filter Matches Outside Unpadded Boundaries"]
G --> H["Project Coordinates to Native Resolution"]
H --> I["MatchResult"]
Maximum Dimension Scaling¶
Given an input image with native dimensions \((W_{\text{orig}}, H_{\text{orig}})\) and optional maximum dimension limit \(D_{\max}\) (max_dim), dimensions are scaled preserving aspect ratio:
Multiple-of-32 Alignment & Normalization¶
Spatial dimensions are aligned to the nearest lower multiples of 32 required by sparse transformer downsampling, and pixel values are normalized to \([0, 1]\) float32 tensors of shape \((1, 1, H, W)\):
Bottom-Right Batch Padding¶
For an image pair \((A, B)\) with individual aligned dimensions \((W_a, H_a)\) and \((W_b, H_b)\), both tensors are bottom-right zero-padded to shared maximum spatial dimensions \((W_{\text{pad}}, H_{\text{pad}})\):
Post-processing: Boundary Filtering & Coordinate Projection¶
Matches \((P_0, P_1)\) detected inside bottom-right zero-padded regions are filtered out using boundary mask \(V\):
Valid raw coordinates \(P_{\text{raw}} = (x_{\text{raw}}, y_{\text{raw}})\) are projected back to native image dimensions \((W_{\text{orig}}, H_{\text{orig}})\):
Numerical Parity Verification¶
Numerical parity verifies mathematical equivalence between the original PyTorch implementation and the exported ONNX Runtime CUDA graph.
In sparse transformer models, keypoint extraction relies on top-k sorting and parallel score reduction. Due to non-associative floating-point operations across parallel GPU threads, PyTorch and ONNX Runtime may produce keypoints in slightly different array order. SpatialHub embeds every matched point pair into a 4D spatial vector \((x_0, y_0, x_1, y_1)\) and resolves correspondences using KD-tree (scipy.spatial.cKDTree) nearest-neighbor queries.
Parity was evaluated on 30 image pairs from the MegaDepth-1500 outdoor evaluation dataset (max_dim denotes the maximum spatial dimension scaling threshold):
| Variant | Max Dim | Pairs | Mean Matches (PyTorch / ONNX) | Match Ratio | Keypoint MAE (px) | Keypoint Max Diff (px) | Confidence MAE | Confidence Max Diff |
|---|---|---|---|---|---|---|---|---|
full |
640 |
30 | 1091.4 / 1091.4 | 99.9% | 0.0000 | 0.0000 | 0.000534 | 0.005604 |
768 |
30 | 1552.0 / 1552.0 | 100.0% | 0.0000 | 0.0000 | 0.000543 | 0.005379 | |
832 |
30 | 1850.8 / 1850.6 | 100.0% | 0.0000 | 0.0000 | 0.000488 | 0.005781 | |
opt |
640 |
30 | 1150.2 / 1149.9 | 100.0% | 0.0000 | 0.0000 | 0.003513 | 0.023308 |
768 |
30 | 1638.7 / 1638.5 | 100.0% | 0.0000 | 0.0000 | 0.003573 | 0.030832 | |
832 |
30 | 1956.6 / 1956.6 | 100.0% | 0.0000 | 0.0000 | 0.003477 | 0.034790 |
Note
Keypoint coordinate error is \(0.0000\text{ px}\) MAE and Max Diff across all tested configurations. Minor confidence variance (\(\sim 10^{-4}\) in full, \(\sim 10^{-3}\) in opt) stems from floating-point reduction order in the attention layers. Evaluated up to max_dim=832.
To verify parity on a custom dataset organized into two folders with matching filenames (folder_a/, folder_b/):
uv run tools/benchmark/parity_efficient_loftr.py \
--folder-a data/custom_pairs/folder_a \
--folder-b data/custom_pairs/folder_b \
--variant all \
--max-dims 640 768 832 \
--max-pairs 30
Performance Benchmarks¶
Latency, throughput, and host process RAM deltas measured on the first pair of the MegaDepth-1500 outdoor dataset (with native input resolutions of \(1271 \times 953\) for image A and \(1600 \times 1174\) for image B). Measurements reflect 5 unmeasured warmup iterations and 20 timed measurement iterations.
| Variant | Max Dim Limit | Latency Mean (ms) | Median (ms) | P95 (ms) | Throughput (Pairs/s) | Host RAM Delta | Peak VRAM Delta |
|---|---|---|---|---|---|---|---|
full |
640 |
85.42 +/- 5.27 | 85.48 | 96.85 | 11.7 | +564.5 MB | +1808.0 MB |
832 |
205.73 +/- 40.02 | 197.83 | 265.53 | 4.9 | +200.1 MB | +4338.0 MB | |
960 |
358.43 +/- 86.24 | 314.15 | 541.61 | 2.8 | +360.7 MB | +6953.0 MB | |
1024 |
3560.52 +/- 334.86 | 3437.02 | 4224.72 | 0.3 | +1859.1 MB | +6953.0 MB | |
opt |
640 |
121.49 +/- 14.46 | 122.42 | 139.37 | 8.2 | +101.2 MB | +1626.0 MB |
832 |
456.78 +/- 80.48 | 503.46 | 562.17 | 2.2 | +207.7 MB | +4220.0 MB | |
960 |
878.12 +/- 80.74 | 879.32 | 1009.08 | 1.1 | +317.9 MB | +5904.0 MB | |
1024 |
1453.20 +/- 133.25 | 1463.95 | 1660.77 | 0.7 | +490.8 MB | +6953.0 MB |
| Variant | Max Dim Limit | Latency Mean (ms) | Median (ms) | P95 (ms) | Throughput (Pairs/s) | Host RAM Delta |
|---|---|---|---|---|---|---|
full |
640 |
3085.78 +/- 333.01 | 3018.24 | 3690.02 | 0.3 | +1175.2 MB |
832 |
5789.84 +/- 268.09 | 5704.12 | 6189.51 | 0.2 | +2731.2 MB | |
960 |
9123.22 +/- 450.85 | 9049.17 | 9671.42 | 0.1 | +4065.2 MB | |
1024 |
11077.42 +/- 399.25 | 11022.39 | 11817.81 | 0.1 | +3581.9 MB | |
opt |
640 |
2833.60 +/- 120.07 | 2821.49 | 3020.90 | 0.4 | +1332.2 MB |
832 |
6070.13 +/- 1001.03 | 5627.47 | 8153.02 | 0.2 | +1490.8 MB | |
960 |
8201.68 +/- 455.54 | 8263.09 | 8992.29 | 0.1 | +3115.0 MB | |
1024 |
10058.98 +/- 647.43 | 10141.71 | 10939.23 | 0.1 | +3940.5 MB |
SpatialHub Adapter API & Usage¶
from spatialhub import EfficientLoFTR
# Initialize with 'opt' variant and CUDA acceleration
matcher = EfficientLoFTR(
model_type="opt",
providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
)
# Execute matching between two images
result = matcher.match("image_a.jpg", "image_b.jpg", max_dim=1024)
# Render side-by-side visualization
result.visualize(top_k=50, save_path="matches.png")
Tooling & Verification Commands¶
Export PyTorch checkpoint weights to standalone ONNX graphs:
uv run tools/export/export_efficient_loftr.py \
--checkpoint weights/eloftr_outdoor.ckpt \
--output-folder onnx_weight \
--opset 17
| Parameter | Type | Default | Description |
|---|---|---|---|
--variant |
str |
"all" |
Model variant to export (full, opt, or all). |
--checkpoint |
str |
None |
Path to source .ckpt PyTorch weights file (downloaded if omitted). |
--output-folder |
str |
onnx_weight |
Destination directory for exported .onnx model files. |
--width |
int |
640 |
Input image width in pixels (must be a multiple of 32). |
--height |
int |
480 |
Input image height in pixels (must be a multiple of 32). |
--opset |
int |
17 |
ONNX Operator Set version. |
Validate numerical coordinate and confidence parity against PyTorch:
uv run tools/benchmark/parity_efficient_loftr.py \
--folder-a upstream/efficient_loftr/data/0015_pairs/folder_a \
--folder-b upstream/efficient_loftr/data/0015_pairs/folder_b \
--variant all \
--max-dims 640 768 832 \
--max-pairs 30 \
--coord-tol 0.01 \
--conf-tol 0.05 \
--output-file .profile/parity_eloftr.txt
| Parameter | Type | Default | Description |
|---|---|---|---|
--folder-a |
str |
Required | Path to first folder of paired evaluation images. |
--folder-b |
str |
Required | Path to second folder of paired evaluation images. |
--variant |
str |
"all" |
Model variant to test (full, opt, or all). |
--max-dims |
list[str] |
["640", "1024"] |
Input spatial scale limits to evaluate. |
--max-pairs |
int |
None |
Maximum number of image pairs to evaluate. |
--coord-tol |
float |
0.01 |
Keypoint coordinate tolerance threshold in pixels. |
--conf-tol |
float |
0.01 |
Confidence score tolerance threshold. |
--checkpoint |
str |
None |
Optional path to custom PyTorch checkpoint file. |
--model-dir |
str |
None |
Directory containing local .onnx model files. |
--output-file |
str |
None |
File path to write markdown parity report. |
Benchmark execution latency, throughput, and memory deltas:
uv run tools/benchmark/profile_efficient_loftr.py \
--variant all \
--provider all \
--image-a "upstream/efficient_loftr/data/0015_pairs/folder_a/0000.jpg" \
--image-b "upstream/efficient_loftr/data/0015_pairs/folder_b/0000.jpg" \
--max-dims 640 832 960 1024 \
--warmup 5 \
--iters 20 \
--output-file .profile/profile_eloftr.txt
| Parameter | Type | Default | Description |
|---|---|---|---|
--variant |
str |
"all" |
Model variant to profile (full, opt, or all). |
--provider |
str |
"all" |
Execution provider filter (cuda, cpu, or all). |
--image-a |
str |
None |
Path to first evaluation image. |
--image-b |
str |
None |
Path to second evaluation image. |
--resolutions |
list[str] |
["640x480"] |
Synthetic spatial resolutions if no images are given. |
--max-dims |
list[str] |
["640", "1024"] |
Input spatial scale limits to test. |
--warmup |
int |
5 |
Number of unmeasured warmup iterations. |
--iters |
int |
20 |
Number of timed measurement iterations. |
--model-dir |
str |
None |
Directory containing local .onnx model files. |
--output-file |
str |
None |
File path to write markdown summary table. |
Returned Result Data Structure¶
Returns a MatchResult dataclass:
| Attribute | Type | Shape | Description |
|---|---|---|---|
image_a |
str | Path | np.ndarray |
Input | First image reference or NumPy array. |
image_b |
str | Path | np.ndarray |
Input | Second image reference or NumPy array. |
keypoints_a |
np.ndarray |
(N, 2) float32 |
Verified keypoint [x, y] coordinates in image_a. |
keypoints_b |
np.ndarray |
(N, 2) float32 |
Verified keypoint [x, y] coordinates in image_b. |
confidence |
np.ndarray |
(N,) float32 |
Match confidence scores in [0.0, 1.0]. |