Install LoMa
mainInstall LoMa in your Python environment (tested on Linux with Python 3.12) using uv:
uv pip install -e .Or use:
uv syncrepository·main·Indexed 18 days ago
https://github.com/davnords/lomaA family of fast and accurate local feature matchers designed as drop-in replacements for SfM and Visual Localization pipelines. LoMa provides multiple model configurations including LoMa-B, LoMa-B128, LoMa-L, LoMa-G, and a rotation-invariant LoMa-R for aerial imagery. The library includes the DeDoDeDescriptor for local feature extraction, DinoVisionTransformer helpers, and geometry utilities for coordinate conversion, warp consistency, and relative pose estimation using RANSAC.
Install LoMa in your Python environment (tested on Linux with Python 3.12) using uv:
uv pip install -e .Or use:
uv syncTo perform local feature matching, import LoMa and a model configuration class (like LoMaB) from loma. Initialize the LoMa object with the desired model, then call .match(img_A_path, img_B_path) to get matching keypoints in image coordinates.
Available model configurations include:
LoMaB (standard size, similar to LightGlue)LoMaB128LoMaL (Large)LoMaG (Great/Heavy - most accurate)LoMaR (Rotation invariant, ideal for aerial imagery)import cv2
from loma import LoMa, LoMaB
# load pretrained model
model = LoMa(LoMaB())
# Define image paths
img_A_path, img_B_path = "assets/0015_A.jpg", "assets/0015_B.jpg"
# Extract matching keypoints in image coordinates
kptsA, kptsB = model.match(img_A_path, img_B_path)
# Example: Find a fundamental matrix using the matches
F, mask = cv2.findFundamentalMat(
kptsA, kptsB, ransacReprojThreshold=0.2, method=cv2.USAC_MAGSAC, confidence=0.999999, maxIters=10000
)To evaluate LoMa on benchmarks like MegaDepth, ScanNet, WxBS, or RUBIK:
source scripts/eval_prep.shuv sync --extra evalUse uv run eval.py --help to see all available options.
uv run eval.py matcher:loma-b --benchmark wxbsYou can run the provided demonstration script using the uv CLI. Specify the matcher using the matcher: prefix.
uv run demo.py matcher:loma-bThe LoMa.Cfg dataclass allows fine-grained control over the model architecture and behavior.
Key Configuration Options:
input_dim: Dimension of input descriptors.embed_dim: Dimension of the transformer embeddings.n_layers: Number of transformer layers.num_heads: Number of attention heads.filter_threshold: Threshold for matching.mp: Boolean to enable mixed precision (AMP).compile: Boolean to enable torch.compile (Linux only).descriptor: Choice of descriptor architecture ("dedode_b" or "dedode_g").num_keypoints: Default number of keypoints to detect.posenc_type: Type of positional encoding ("learnable" or "fixed").posenc_gamma: Wavelength/scale for positional encoding.weights_url: URL to download pretrained weights.The DeDoDeDescriptor.Cfg dataclass controls the initialization of the descriptor model. Use these keys to tune the model architecture and performance:
| Key | Type | Default | Description |
|---|---|---|---|
arch | Literal["dedode_b", "dedode_g"] | "dedode_b" | The model architecture to use. |
compile | bool | True | Whether to apply torch.compile() to the model. |
descriptor_dim | int | 256 | The dimensionality of the output descriptors. |
hidden_blocks | int | 5 | The number of hidden blocks in the ConvRefiner layers. |
The FineFeatures.Cfg dataclass defines the configuration for feature extraction models.
| Key | Type | Default | Description |
|---|---|---|---|
type | FineFeaturesType | "vgg19bn" | The type of feature extractor to use. |
patch_size | int | 4 | Determines the depth of the VGG layers used. Valid keys for mapping to layer indices are {1, 2, 4, 8, 16}. |
from loma.features import FineFeatures
# Example: Using a different patch size
cfg = FineFeatures.Cfg(type="vgg19bn", patch_size=8)
model = FineFeatures(cfg)The DinoVisionTransformer class can be customized via its constructor. Key parameters include:
img_size / patch_size: Dimensions for input and patch segmentation.embed_dim: The embedding dimension of the transformer.depth: Number of transformer blocks.num_heads: Number of attention heads.mlp_ratio: Ratio of MLP hidden dimension to embedding dimension.ffn_layer: Type of Feed-Forward Network. Options: "mlp" (default), "identity" (returns nn.Identity), or other custom implementations.block_chunks: (int) Splits the block sequence into block_chunks units. This is used for FSDP (Fully Sharded Data Parallel) wrapping.drop_path_rate: Stochastic depth rate.drop_path_uniform: If True, applies the same drop_path_rate to all blocks; otherwise, applies a decay rule.from loma.descriptor.transformer.dinov2 import DinoVisionTransformer
model = DinoVisionTransformer(
img_size=224,
patch_size=16,
embed_dim=768,
depth=12,
num_heads=12,
block_chunks=2 # Split blocks into 2 chunks for FSDP
)LoMa provides several model variants with different trade-offs between speed and accuracy:
| Model | Description |
|---|---|
LoMa-B | Standard size, similar to LightGlue. Good for most use cases. |
LoMa-B128 | Variant of the B model. |
LoMa-L | Large model. |
LoMa-G | Heavy model. Most accurate, surpasses RoMa-family on difficult benchmarks. |
LoMa-R | Rotation invariant model. Optimized for aerial imagery. |
check_not_i16(pil_img) function checks if a PIL Image is in the `Access the frozen detector directly to get keypoints from a batch of images.
Arguments:
batch: A dictionary containing img_A and/or img_B (and optionally img).num_keypoints (optional): Number of keypoints to detect.Returns:
dict: A dictionary containing the detected keypoints.# batch should follow the Batch type definition
results = model.detect(batch, num_keypoints=1024)
keypoints = results["keypoints"]Provides multiple ways to estimate the relative rotation ($R$) and translation ($t$) between two camera views based on point correspondences.
estimate_pose_cv2_ransac: Uses OpenCV's findEssentialMat and recoverPose. Requires normalized keypoints and camera intrinsics.estimate_pose_essential: Uses the poselib library for robust relative pose estimation.estimate_pose_fundamental: Estimates the Fundamental matrix using poselib and then recovers $R$ and $t$ via OpenCV.import numpy as np
from loma.geometry import estimate_pose_cv2_ransac
# kpts0, kpts1: (N, 2) arrays
# K0, K1: (3, 3) intrinsic matrices
# norm_thresh: RANSAC threshold
result = estimate_pose_cv2_ransac(kpts0, kpts1, K0, K1, norm_thresh=1.0)
if result:
R, t, mask = result