using-git-worktrees
OBobra/superpowers298k2,631 new stars in the last 7 days
Integrate a new image matching model into the image-matching-webui project. Invoke when user provides a GitHub repo URL of a feature matching method and asks to add/integrate it.
This skill automates the integration of a new local feature matching method into the image-matching-webui project. Given a GitHub repository URL, it follows the project's established patterns to add the matcher as a fully functional option in the WebUI.
image-matching-webui project root/tmp/<repo-name> for analysis (do NOT add as submodule yet)required_inputs = ["image0", "image1"]required_inputs includes keypoints0, descriptors0, etc.pyproject.toml or requirements.txttorch.autocast or mixed precision (mp, amp)?loma.device)?git submodule add <repo-url> imcui/third_party/<RepoName>
Vincentqyw/xxx or agipro/xxx), prefer the forkimcui/third_party/ contains pinned third-party submodules — you are NOT the owner of this code. If a dependency needs a compatibility fix (e.g., API changes in PyTorch/kornia, import path changes):
agipro GitHub accountgit submodule deinit -f imcui/third_party/<RepoName>
git rm -f imcui/third_party/<RepoName>
rm -rf .git/modules/imcui/third_party/<RepoName>
git submodule add https://github.com/agipro/<RepoName>.git imcui/third_party/<RepoName>
.gitmodules — the URL must point to the forkExample: EfficientLoFTR was forked to agipro/EfficientLoFTR to fix a kornia.utils.grid → kornia.utils import change required by kornia 0.8+.
Create imcui/hloc/matchers/<matcher-name>.py following these rules:
import sys
from pathlib import Path
from .. import logger
from ..utils.base_model import BaseModel
# Add third_party to sys.path for import
<matcher>_path = Path(__file__).parent / "../../third_party/<RepoName>/src"
sys.path.append(str(<matcher>_path))
# Import the model class
from <module> import <ModelClass>
src/ as the package root, append <path>/srcThe class MUST:
BaseModelBaseModel subclass in the file (the dynamic_load function asserts this)default_conf dict with all configurable parametersrequired_inputs listclass MatcherName(BaseModel):
default_conf = {
"name": "two_view_pipeline",
"model_name": "<default_variant>",
"max_keypoints": 2048,
# ... other config
}
required_inputs = [
"image0",
"image1",
# For sparse matchers, also include:
# "keypoints0", "scores0", "descriptors0",
# "keypoints1", "scores1", "descriptors1",
]
_init Methodself.conf (not the conf parameter) to access merged configlogger.info()self._download_model(repo_id=MODEL_REPO_ID, filename=...)torch.hub), let it handleif not torch.cuda.is_available():
# Disable mp/amp for CPU/MPS compatibility
_forward MethodFor standalone matchers (input: raw images):
The data dict contains preprocessed tensors:
data["image0"]: shape (1, C, H, W), float32, range [0, 1]data["image1"]: shape (1, C, H, W)`, float32, range [0, 1]If the model expects PIL images or file paths, convert:
img0 = data["image0"].cpu().numpy().squeeze() * 255
img0 = img0.transpose(1, 2, 0) # CHW -> HWC
img0 = Image.fromarray(img0.astype("uint8"))
For sparse matchers (input: keypoints + descriptors):
# Repackage data for the model's expected format
input = {
"image0": {"image": data["image0"], "keypoints": data["keypoints0"], ...},
"image1": {"image": data["image1"], "keypoints": data["keypoints1"], ...},
}
return self.net(input)
The _forward method MUST return a dict with these keys:
Dense matchers that output matched keypoints only:
pred = {
"keypoints0": kpts0, # torch.Tensor, shape (N, 2), pixel coords in resized image
"keypoints1": kpts1, # torch.Tensor, shape (N, 2)
"mconf": confidence, # torch.Tensor, shape (N,), match confidence scores
}
Dense matchers that can separate detected vs matched keypoints (PREFERRED):
pred = {
"keypoints0": all_kpts0, # All detected keypoints (for UI "Keypoints" display)
"keypoints1": all_kpts1, # All detected keypoints
"mkeypoints0": matched_kpts0, # Matched keypoints (for UI match lines)
"mkeypoints1": matched_kpts1, # Matched keypoints
"mconf": confidence, # Match confidence scores
}
Sparse matchers (LightGlue-style):
# Return the model's raw output — match_dense.py handles the rest
return self.net(input)
Key output rules:
keypoints0/1: ALL detected keypoints (shown in UI "Open for More: Keypoints")mkeypoints0/1: Only MATCHED keypoints (shown as match lines in UI)mkeypoints0/1 is missing, match_dense.py falls back to keypoints0/1mconf should be real confidence scores (not all-ones)imcui/hloc/configs/matchers.pyAdd a configuration entry for each model variant:
"<matcher-name>": {
"output": "matches-<matcher-name>",
"model": {
"name": "<matcher-module-name>", # Must match the .py filename in matchers/
"model_name": "<variant>", # Passed as conf["model_name"]
"max_keypoints": 2048,
# ... other model-specific config
},
"preprocessing": {
"grayscale": False, # True for LoFTR-style; False for most modern matchers
"force_resize": True,
"resize_max": 1024,
"width": 640,
"height": 480,
"dfactor": 8, # Image dimensions must be divisible by this
},
},
Key rules:
"name" in model must match the Python filename (e.g., "loma" → loma.py)loma-b, loma-l, loma-g)preprocessing.grayscale: Set True only for models that expect 1-channel input (e.g., LoFTR)preprocessing.dfactor: Ensure resized dimensions are divisible by this valueconfig/app.yaml (local dev) AND imcui/config/app.yaml (package default)BOTH files must be updated identically. Add an entry under matcher_zoo:
<DisplayName>:
matcher: <matcher-config-name> # Must match key in matchers.py
standalone: true # true = takes two images directly (no separate extractor needed)
skip_ci: false # DEFAULT: false. Only set to true for heavy matchers (see rule below)
info:
name: <DisplayName> # Display name in WebUI dropdown
source: "Venue Year" # e.g., "ECCV 2026", "ICCV 2023"
paper: <paper-url> # arXiv or published paper URL
github: <github-url> # Official GitHub repo
display: true # Whether to show in WebUI
efficiency: medium # low (heavy), medium, high (fast)
Key rules:
standalone: true means the matcher takes two raw images directly (no separate feature extractor needed)standalone: false means the matcher requires a feature extractor (e.g., SuperPoint+LightGlue)<extractor>+<matcher> format (e.g., superpoint+lightglue)enable: false for very heavy models that most users won't use by defaultconfig/app.yaml and imcui/config/app.yaml must be kept in syncskip_ci ruleskip_ci defaults to false — you can omit the field entirely for most matchers. Only set skip_ci: true when the model is too heavy to run in CI (GitHub Actions CPU-only Ubuntu runner with 7GB RAM):
| Condition | skip_ci |
|---|---|
| Lightweight matcher (e.g., LightGlue, SuperGlue, XFeat, ALIKED-based) | false (or omit) |
| Heavy dense matcher prone to CI OOM/timeout (e.g., RoMa, DKM, GIM, Mast3R, LoMa-L/G/R, MINIMA large) | true |
Rule of thumb: if the model has variants like B/L/G/R, the "B" (base) variant usually passes CI, larger ones may not. When unsure, omit skip_ci (defaults to false) and let CI tell you — if it OOMs, set it to true in a follow-up commit.
Common issues and fixes:
On MPS/CPU, torch.autocast with float16/bfloat16 causes:
RuntimeError: Input type (c10::Half) and bias type (float) should be the sameRuntimeError: Input type (MPSFloatType) and weight type (torch.FloatTensor) should be the sameFix pattern (before importing third-party code):
# Patch module-level amp_dtype before import
import <module>.device as _device
if not torch.cuda.is_available():
_device.amp_dtype = torch.float32
# Also patch submodule local bindings
import <module>.submod as _submod
if not torch.cuda.is_available():
_submod.amp_dtype = torch.float32
After model construction:
if not torch.cuda.is_available():
cfg = dataclasses.replace(cfg, mp=False)
for module in self.net.modules():
if hasattr(module, "amp"):
module.amp = False
If the model manages its own device (like LoMa's loma.device):
# Override .to() to keep model on its expected device
def to(self, device=None, **kwargs):
return super().to(<model_expected_device>, **kwargs)
If a model uses @torch.inference_mode() but you need to pass its outputs to another module that requires grad tracking:
with torch.no_grad():
output = model.detect_and_describe(...)
output = output.clone() # Detach from inference mode graph
The README.md contains a "The tool currently supports..." table listing all algorithms with their support status.
| Algorithm | Supported | Conference/Journal | Year | GitHub Link |
|------------------|-----------|--------------------|------|-------------|
| LoMa | ✅ | ECCV | 2026 | [Link](https://github.com/davnords/LoMa) |
| RIPE | ✅ | ICCV | 2025 | [Link](https://github.com/fraunhoferhhi/RIPE) |
Rows are sorted by year descending, then alphabetically by algorithm name within the same year.
| Scenario | Action |
|---|---|
Algorithm exists with ❌ | Change to ✅ |
| Algorithm not in table | Add new row, maintaining sort order |
Algorithm already has ✅ | Skip (no change needed) |
After integrating "DaD" published at ARXIV 2025:
| DaD | ✅ | ARXIV | 2025 | [Link](https://github.com/Parskatt/dad) |
Insert between RIPE (ICCV 2025) and MINIMA (ARXIV 2024).
Before committing, pre-commit MUST pass. This is non-negotiable — commits that fail pre-commit will be rejected at the PR stage.
# Run ALL pre-commit hooks on all files
pre-commit run -a
# Or run specific hooks if you only changed certain files
pre-commit run ruff --all-files # Python linting
pre-commit run mypy --all-files # Type checking (if new Python code)
Common issues and fixes:
| Hook | Common failure | Fix |
|---|---|---|
ruff | unused imports, line too long | Remove unused imports, wrap long lines |
ruff-format | inconsistent indentation | Let ruff format: ruff format <file> |
mypy | missing type annotations | Add type hints to new functions |
trailing-whitespace | blank lines with spaces | Strip trailing whitespace |
check-yaml | invalid YAML syntax | Fix indentation/quoting in app.yaml |
end-of-file-fixer | missing newline at EOF | Add trailing newline |
If pre-commit run -a fails, fix all errors before proceeding to commit. Do NOT skip hooks with --no-verify.
After integration and pre-commit pass, verify:
python -c "from imcui.hloc.matchers import <name>"python app.py → select the matcher → run matchingconfig/app.yaml and imcui/config/app.yaml are identical for the new matcherpre-commit run -a exits with zeroOnce pre-commit passes and all verification checks are green:
# Stage the changes
git add .gitmodules imcui/third_party/<RepoName>
git add imcui/hloc/matchers/<name>.py
git add imcui/hloc/configs/matchers.py
git add imcui/config/app.yaml
git add config/app.yaml
git add README.md
# Commit with a descriptive message following convention
git commit -m "feat: integrate <MatcherName>
Add <MatcherName> matcher from <venue> <year>.
- Add submodule: imcui/third_party/<RepoName>
- Add matcher implementation: imcui/hloc/matchers/<name>.py
- Add matcher config entries
- Update app.yaml matcher zoo (both package and user configs)
- Update README.md algorithm table
Co-Authored-By: Claude <[email protected]>"
# Push and create PR
git push origin main
gh pr create --title "feat: integrate <MatcherName>" \
--body "Integrate <MatcherName> matcher from <paper-link>.
## Changes
- [x] Submodule added
- [x] Matcher implementation
- [x] Config entries in both app.yaml files
- [x] README.md updated
- [x] Pre-commit passes
## Tested on
- [ ] CPU
- [ ] CUDA
- [ ] MPS
🤖 Generated with [Claude Code](https://claude.com/claude-code)"
Important notes:
feat: prefix for new matcher integrations (following conventional commits)Co-Authored-By: Claude <[email protected]> in commit messages🤖 Generated with [Claude Code](https://claude.com/claude-code)skip_ci: true in a follow-up commitFor each integration, these files are typically modified:
| File | Action | Description |
|---|---|---|
.gitmodules | Modify | Add submodule entry |
imcui/third_party/<RepoName> | Add | Git submodule |
imcui/hloc/matchers/<name>.py | Create | Matcher implementation |
imcui/hloc/configs/matchers.py | Modify | Add matcher config entries |
config/app.yaml | Modify | Add WebUI display config |
imcui/config/app.yaml | Modify | Add WebUI display config (must match config/app.yaml) |
README.md | Modify | Update supported algorithms table (add row or flip ❌→✅) |
Before committing all changes, run pre-commit run -a — all hooks must pass.
| Pattern | File | Description |
|---|---|---|
| Dense standalone | imcui/hloc/matchers/roma.py | RoMa — raw images → matched keypoints |
| Dense standalone + separate detected/matched kpts | imcui/hloc/matchers/loma.py | LoMa — separates all detected from matched keypoints |
| Sparse matcher | imcui/hloc/matchers/lightglue.py | LightGlue — keypoints+descriptors → matches |
| Detector-only + external descriptor | imcui/hloc/extractors/raco.py | RaCo — detects keypoints, delegates descriptor to ALIKED |
Some models are keypoint detectors only — they output keypoints/scores but no descriptors. The RaCo integration demonstrates chaining RaCo detection with ALIKED description:
imcui/hloc/extractors/raco.py_forward, first run RaCo detection → keypoints, then run ALIKED descriptor on those keypointsconfigs/extractors.py with max_num_keypoints / nms_radius parametersconfigs/matchers.py with features: "raco-aliked" — a custom LightGlue+ checkpoint trained for RaCo+ALIKED featuresapp.yaml, the entry uses feature: raco (the extractor) + matcher: raco-lightglue (the LightGlue variant)When a submodule needs a compatibility fix:
| Repo owner | Action |
|---|---|
Vincentqyw/* | Push fix directly to the Vincentqyw fork |
agipro/* | Push fix directly to the agipro fork |
| Anyone else | Fork to agipro, apply fix, switch submodule URL |
Always verify push succeeded with git log --oneline -1 in the submodule directory.
Kornia 0.8.0 removed the kornia.utils.grid submodule. create_meshgrid moved to kornia.utils (0.8.0-0.8.2), then to kornia.geometry (0.8.3+). When fixing submodule imports, use this future-proof pattern:
try:
from kornia.geometry import create_meshgrid # kornia >= 0.8.3
except ImportError:
from kornia.utils import create_meshgrid # kornia < 0.8.3
OBobra/superpowers298k2,631 new stars in the last 7 days
OBobra/superpowers298k2,631 new stars in the last 7 days
OBobra/superpowers298k2,631 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days