Files
kornia--kornia/tests/feature/test_matching.py
T
wehub-resource-sync 3a2c66702c
Tests on CPU (scheduled) / check-skip (push) Has been cancelled
Tests on CPU (scheduled) / pre-tests (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-ubuntu (float32) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-ubuntu (float64) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float64, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float64, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float64, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float64, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.13, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.13, float64, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.11, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.11, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.12, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.12, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.13, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / coverage (push) Has been cancelled
Tests on CPU (scheduled) / typing (push) Has been cancelled
Tests on CPU (scheduled) / tutorials (push) Has been cancelled
Tests on CPU (scheduled) / docs (push) Has been cancelled
Lint / TOML Format (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:27 +08:00

612 lines
28 KiB
Python

# LICENSE HEADER MANAGED BY add-license-header
#
# Copyright 2018 Kornia Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import pytest
import torch
from kornia.core._compat import torch_version_le
from kornia.feature.integrated import LightGlueMatcher
from kornia.feature.laf import laf_from_center_scale_ori
from kornia.feature.matching import (
DescriptorMatcher,
DescriptorMatcherWithSteerer,
GeometryAwareDescriptorMatcher,
match_adalam,
match_fginn,
match_mnn,
match_nn,
match_smnn,
match_snn,
)
from kornia.feature.steerers import DiscreteSteerer
from testing.base import BaseTester
from testing.casts import dict_to
class TestMatchNN(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(1, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
desc2 = torch.rand(num_desc2, dim, device=device)
dists, idxs = match_nn(desc1, desc2)
assert idxs.shape == (num_desc1, 2)
assert dists.shape == (num_desc1, 1)
def test_matching(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
dists, idxs = match_nn(desc1, desc2)
expected_dists = torch.tensor([0, 0, 0.5, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
dists1, idxs1 = match_nn(desc1, desc2)
self.assert_close(dists1, expected_dists)
self.assert_close(idxs1, expected_idx)
def test_gradcheck(self, device):
desc1 = torch.rand(5, 8, device=device, dtype=torch.float64)
desc2 = torch.rand(7, 8, device=device, dtype=torch.float64)
self.gradcheck(match_mnn, (desc1, desc2), nondet_tol=1e-4)
class TestMatchMNN(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(1, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
desc2 = torch.rand(num_desc2, dim, device=device)
dists, idxs = match_mnn(desc1, desc2)
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= num_desc1
def test_matching(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
dists, idxs = match_mnn(desc1, desc2)
expected_dists = torch.tensor([0, 0, 0.5, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
matcher = DescriptorMatcher("mnn").to(device)
dists1, idxs1 = matcher(desc1, desc2)
self.assert_close(dists1, expected_dists)
self.assert_close(idxs1, expected_idx)
def test_gradcheck(self, device):
desc1 = torch.rand(5, 8, device=device, dtype=torch.float64)
desc2 = torch.rand(7, 8, device=device, dtype=torch.float64)
self.gradcheck(match_mnn, (desc1, desc2), nondet_tol=1e-4)
class TestMatchSNN(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(2, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
desc2 = torch.rand(num_desc2, dim, device=device)
dists, idxs = match_snn(desc1, desc2)
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= num_desc1
def test_nomatch(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0]], device=device)
dists, idxs = match_snn(desc1, desc2, 0.8)
assert len(dists) == 0
assert len(idxs) == 0
def test_matching1(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
dists, idxs = match_snn(desc1, desc2, 0.8)
expected_dists = torch.tensor([0, 0, 0.35355339059327373, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
matcher = DescriptorMatcher("snn", 0.8).to(device)
dists1, idxs1 = matcher(desc1, desc2)
self.assert_close(dists1, expected_dists)
self.assert_close(idxs1, expected_idx)
def test_matching2(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
dists, idxs = match_snn(desc1, desc2, 0.1)
expected_dists = torch.tensor([0.0, 0, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
matcher = DescriptorMatcher("snn", 0.1).to(device)
dists1, idxs1 = matcher(desc1, desc2)
self.assert_close(dists1, expected_dists)
self.assert_close(idxs1, expected_idx)
def test_gradcheck(self, device):
desc1 = torch.rand(5, 8, device=device, dtype=torch.float64)
desc2 = torch.rand(7, 8, device=device, dtype=torch.float64)
self.gradcheck(match_snn, (desc1, desc2, 0.8), nondet_tol=1e-4)
class TestMatchSMNN(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(2, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
desc2 = torch.rand(num_desc2, dim, device=device)
dists, idxs = match_smnn(desc1, desc2, 0.8)
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= num_desc1
assert dists.shape[0] <= num_desc2
def test_matching1(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
dists, idxs = match_smnn(desc1, desc2, 0.8)
expected_dists = torch.tensor([0, 0, 0.5423, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
matcher = DescriptorMatcher("smnn", 0.8).to(device)
dists1, idxs1 = matcher(desc1, desc2)
self.assert_close(dists1, expected_dists)
self.assert_close(idxs1, expected_idx)
def test_nomatch(self, device):
desc1 = torch.tensor([[0, 0.0]], device=device)
desc2 = torch.tensor([[5, 5.0]], device=device)
dists, idxs = match_smnn(desc1, desc2, 0.8)
assert len(dists) == 0
assert len(idxs) == 0
def test_matching2(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
dists, idxs = match_smnn(desc1, desc2, 0.1)
expected_dists = torch.tensor([0.0, 0, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
matcher = DescriptorMatcher("smnn", 0.1).to(device)
dists1, idxs1 = matcher(desc1, desc2)
self.assert_close(dists1, expected_dists)
self.assert_close(idxs1, expected_idx)
@pytest.mark.parametrize(
"match_type, d1, d2",
[
("nn", 0, 10),
("nn", 10, 0),
("nn", 0, 0),
("snn", 0, 10),
("snn", 10, 0),
("snn", 0, 0),
("mnn", 0, 10),
("mnn", 10, 0),
("mnn", 0, 0),
("smnn", 0, 10),
("smnn", 10, 0),
("smnn", 0, 0),
],
)
def test_empty_nocrash(self, match_type, d1, d2, device, dtype):
desc1 = torch.empty(d1, 8, device=device, dtype=dtype)
desc2 = torch.empty(d2, 8, device=device, dtype=dtype)
matcher = DescriptorMatcher(match_type, 0.8).to(device)
dists, idxs = matcher(desc1, desc2)
assert dists is not None
assert idxs is not None
def test_gradcheck(self, device):
desc1 = torch.rand(5, 8, device=device, dtype=torch.float64)
desc2 = torch.rand(7, 8, device=device, dtype=torch.float64)
matcher = DescriptorMatcher("smnn", 0.8).to(device)
self.gradcheck(match_smnn, (desc1, desc2, 0.8), nondet_tol=1e-4)
self.gradcheck(matcher, (desc1, desc2), nondet_tol=1e-4)
@pytest.mark.jit()
@pytest.mark.parametrize("match_type", ["nn", "snn", "mnn", "smnn"])
def test_jit(self, match_type, device, dtype):
desc1 = torch.rand(5, 8, device=device, dtype=dtype)
desc2 = torch.rand(7, 8, device=device, dtype=dtype)
matcher = DescriptorMatcher(match_type, 0.8).to(device)
matcher_jit = torch.jit.script(DescriptorMatcher(match_type, 0.8).to(device))
self.assert_close(matcher(desc1, desc2)[0], matcher_jit(desc1, desc2)[0])
self.assert_close(matcher(desc1, desc2)[1], matcher_jit(desc1, desc2)[1])
class TestMatchFGINN(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(2, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape_one_way(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
desc2 = torch.rand(num_desc2, dim, device=device)
lafs1 = torch.rand(1, num_desc1, 2, 3, device=device)
lafs2 = torch.rand(1, num_desc2, 2, 3, device=device)
dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, 0.9, 1000)
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= num_desc1
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(2, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape_two_way(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
desc2 = torch.rand(num_desc2, dim, device=device)
lafs1 = torch.rand(1, num_desc1, 2, 3, device=device)
lafs2 = torch.rand(1, num_desc2, 2, 3, device=device)
dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, 0.9, 1000, mutual=True)
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= num_desc1
assert dists.shape[0] <= num_desc2
def test_matching1(self, device, dtype):
desc1 = torch.tensor([[0, 0.0], [1, 1.001], [2, 2], [3, 3.0], [5, 5.0]], dtype=dtype, device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1.001], [0, 0.0]], dtype=dtype, device=device)
lafs1 = laf_from_center_scale_ori(desc1[None])
lafs2 = laf_from_center_scale_ori(desc2[None])
dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, 0.8, 0.01)
expected_dists = torch.tensor([0, 0, 0.3536, 0, 0], dtype=dtype, device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists, rtol=0.001, atol=1e-3)
self.assert_close(idxs, expected_idx)
matcher = GeometryAwareDescriptorMatcher("fginn", {"spatial_th": 0.01}).to(device)
dists1, idxs1 = matcher(desc1, desc2, lafs1, lafs2)
self.assert_close(dists1, expected_dists, rtol=0.001, atol=1e-3)
self.assert_close(idxs1, expected_idx)
def test_matching_mutual(self, device, dtype):
desc1 = torch.tensor([[0, 0.1], [1, 1.001], [2, 2], [3, 3.0], [5, 5.0], [0.0, 0]], dtype=dtype, device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1.001], [0, 0.0]], dtype=dtype, device=device)
lafs1 = laf_from_center_scale_ori(desc1[None])
lafs2 = laf_from_center_scale_ori(desc2[None])
dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, 0.8, 2.0, mutual=True)
expected_dists = torch.tensor([0, 0.1768, 0, 0, 0], dtype=dtype, device=device).view(-1, 1)
expected_idx = torch.tensor([[1, 3], [2, 2], [3, 1], [4, 0], [5, 4]], device=device)
self.assert_close(dists, expected_dists, rtol=0.001, atol=1e-3)
self.assert_close(idxs, expected_idx)
matcher = GeometryAwareDescriptorMatcher("fginn", {"spatial_th": 2.0, "mutual": True}).to(device)
dists1, idxs1 = matcher(desc1, desc2, lafs1, lafs2)
self.assert_close(dists1, expected_dists, rtol=0.001, atol=1e-3)
self.assert_close(idxs1, expected_idx)
def test_nomatch(self, device, dtype):
desc1 = torch.tensor([[0, 0.0]], dtype=dtype, device=device)
desc2 = torch.tensor([[5, 5.0]], dtype=dtype, device=device)
lafs1 = laf_from_center_scale_ori(desc1[None])
lafs2 = laf_from_center_scale_ori(desc2[None])
dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, 0.8)
assert len(dists) == 0
assert len(idxs) == 0
def test_matching2(self, device, dtype):
desc1 = torch.tensor([[0, 0.0], [1, 1.001], [2, 2], [3, 3.0], [5, 5.0]], dtype=dtype, device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1.001], [0, 0.0]], dtype=dtype, device=device)
lafs1 = laf_from_center_scale_ori(desc1[None])
lafs2 = laf_from_center_scale_ori(desc2[None])
dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, 0.8, 2.0)
expected_dists = torch.tensor([0, 0, 0.1768, 0, 0], dtype=dtype, device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists, rtol=0.001, atol=1e-3)
self.assert_close(idxs, expected_idx)
matcher = GeometryAwareDescriptorMatcher("fginn", {"spatial_th": 2.0}).to(device)
dists1, idxs1 = matcher(desc1, desc2, lafs1, lafs2)
self.assert_close(dists1, expected_dists, rtol=0.001, atol=1e-3)
self.assert_close(idxs1, expected_idx)
def test_gradcheck(self, device):
desc1 = torch.rand(5, 8, device=device, dtype=torch.float64)
desc2 = torch.rand(7, 8, device=device, dtype=torch.float64)
center1 = torch.rand(1, 5, 2, device=device, dtype=torch.float64)
center2 = torch.rand(1, 7, 2, device=device, dtype=torch.float64)
lafs1 = laf_from_center_scale_ori(center1)
lafs2 = laf_from_center_scale_ori(center2)
self.gradcheck(match_fginn, (desc1, desc2, lafs1, lafs2, 0.8, 0.05), nondet_tol=1e-4)
@pytest.mark.jit()
@pytest.mark.skip("keyword-arg expansion is not supported")
def test_jit(self, device, dtype):
desc1 = torch.rand(5, 8, device=device, dtype=dtype)
desc2 = torch.rand(7, 8, device=device, dtype=dtype)
center1 = torch.rand(1, 5, 2, device=device)
center2 = torch.rand(1, 7, 2, device=device)
lafs1 = laf_from_center_scale_ori(center1)
lafs2 = laf_from_center_scale_ori(center2)
matcher = GeometryAwareDescriptorMatcher("fginn", 0.8).to(device)
matcher_jit = torch.jit.script(GeometryAwareDescriptorMatcher("fginn", 0.8).to(device))
self.assert_close(matcher(desc1, desc2)[0], matcher_jit(desc1, desc2, lafs1, lafs2)[0])
self.assert_close(matcher(desc1, desc2)[1], matcher_jit(desc1, desc2, lafs1, lafs2)[1])
class TestAdalam(BaseTester):
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_real(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
with torch.no_grad():
dists, idxs = match_adalam(data_dev["descs1"], data_dev["descs2"], data_dev["lafs1"], data_dev["lafs2"])
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= data_dev["descs1"].shape[0]
assert dists.shape[0] <= data_dev["descs2"].shape[0]
expected_idxs = data_dev["expected_idxs"].long()
self.assert_close(idxs, expected_idxs, rtol=1e-4, atol=1e-4)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_single_nocrash(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
with torch.no_grad():
_dists, _idxs = match_adalam(
data_dev["descs1"], data_dev["descs2"][:1], data_dev["lafs1"], data_dev["lafs2"][:, :1]
)
_dists, _idxs = match_adalam(
data_dev["descs1"][:1], data_dev["descs2"], data_dev["lafs1"][:, :1], data_dev["lafs2"]
)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_small_user_conf(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
adalam_config = {"device": device}
with torch.no_grad():
_dists, _idxs = match_adalam(
data_dev["descs1"], data_dev["descs2"][:1], data_dev["lafs1"], data_dev["lafs2"][:, :1]
)
_dists, _idxs = match_adalam(
data_dev["descs1"], data_dev["descs2"], data_dev["lafs1"], data_dev["lafs2"], config=adalam_config
)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_empty_nocrash(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
with torch.no_grad():
_dists, _idxs = match_adalam(
data_dev["descs1"],
torch.empty(0, 128, device=device, dtype=dtype),
data_dev["lafs1"],
torch.empty(0, 0, 2, 3, device=device, dtype=dtype),
)
_dists, _idxs = match_adalam(
torch.empty(0, 128, device=device, dtype=dtype),
data_dev["descs2"],
torch.empty(0, 0, 2, 3, device=device, dtype=dtype),
data_dev["lafs2"],
)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_small(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
with torch.no_grad():
_dists, _idxs = match_adalam(
data_dev["descs1"][:4], data_dev["descs2"][:4], data_dev["lafs1"][:, :4], data_dev["lafs2"][:, :4]
)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_seeds_fail(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
with torch.no_grad():
_dists, _idxs = match_adalam(
data_dev["descs1"][:100],
data_dev["descs2"][:100],
data_dev["lafs1"][:, :100],
data_dev["lafs2"][:, :100],
)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["adalam_idxs"], indirect=True)
def test_module(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
matcher = GeometryAwareDescriptorMatcher("adalam", {"device": device}).to(device, dtype)
with torch.no_grad():
dists, idxs = matcher(data_dev["descs1"], data_dev["descs2"], data_dev["lafs1"], data_dev["lafs2"])
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= data_dev["descs1"].shape[0]
assert dists.shape[0] <= data_dev["descs2"].shape[0]
expected_idxs = data_dev["expected_idxs"].long()
self.assert_close(idxs, expected_idxs, rtol=1e-4, atol=1e-4)
class TestLightGlueDISK(BaseTester):
@pytest.mark.slow
@pytest.mark.skipif(torch_version_le(1, 9, 1), reason="Needs autocast")
@pytest.mark.parametrize("data", ["lightglue_idxs"], indirect=True)
def test_real(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
config = {"depth_confidence": -1, "width_confidence": -1}
lg = LightGlueMatcher("disk", config).to(device=device, dtype=dtype).eval()
with torch.no_grad():
dists, idxs = lg(data_dev["descs1"], data_dev["descs2"], data_dev["lafs1"], data_dev["lafs2"])
assert idxs.shape[1] == 2
assert dists.shape[1] == 1
assert idxs.shape[0] == dists.shape[0]
assert dists.shape[0] <= data_dev["descs1"].shape[0]
assert dists.shape[0] <= data_dev["descs2"].shape[0]
if device.type == "cpu":
expected_idxs = data_dev["lightglue_disk_idxs"].long()
self.assert_close(idxs, expected_idxs, rtol=1e-4, atol=1e-4)
@pytest.mark.slow
@pytest.mark.parametrize("data", ["lightglue_idxs"], indirect=True)
def test_single_nocrash(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
lg = LightGlueMatcher("disk").to(device, dtype).eval()
with torch.no_grad():
_dists, _idxs = lg(data_dev["descs1"], data_dev["descs2"][:1], data_dev["lafs1"], data_dev["lafs2"][:, :1])
_dists, _idxs = lg(data_dev["descs1"][:1], data_dev["descs2"], data_dev["lafs1"][:, :1], data_dev["lafs2"])
@pytest.mark.slow
@pytest.mark.parametrize("data", ["lightglue_idxs"], indirect=True)
def test_empty_nocrash(self, device, dtype, data):
torch.random.manual_seed(0)
# This is not unit test, but that is quite good integration test
data_dev = dict_to(data, device, dtype)
lg = LightGlueMatcher("disk").to(device, dtype).eval()
with torch.no_grad():
_dists, _idxs = lg(
data_dev["descs1"],
torch.empty(0, 256, device=device, dtype=dtype),
data_dev["lafs1"],
torch.empty(0, 0, 2, 3, device=device, dtype=dtype),
)
_dists, _idxs = lg(
torch.empty(0, 256, device=device, dtype=dtype),
data_dev["descs2"],
torch.empty(0, 0, 2, 3, device=device, dtype=dtype),
data_dev["lafs2"],
)
class TestLightGlueHardNet(BaseTester):
def test_smoke(self):
lg = LightGlueMatcher("doghardnet")
assert isinstance(lg, LightGlueMatcher)
class TestMatchSteererGlobal(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(1, 4, 4), (2, 5, 128), (6, 2, 32), (32, 32, 8)])
@pytest.mark.parametrize("matching_mode", ["nn", "mnn", "snn", "smnn"])
@pytest.mark.parametrize("fast", [False, True])
def test_shape(self, num_desc1, num_desc2, dim, matching_mode, fast, device):
desc1 = torch.rand(num_desc1, dim, device=device)
generator = torch.rand(dim, dim, device=device)
steerer = DiscreteSteerer(generator)
desc2 = steerer(desc1)
matcher = DescriptorMatcherWithSteerer(
steerer=steerer, steerer_order=3, steer_mode="global", match_mode=matching_mode
)
dists, idxs, _num_rot = matcher(
desc1,
desc2,
subset_size=max(1, min(num_desc1 // 2, num_desc2 // 2)) if fast else None,
)
assert dists.shape[1] == 1
assert dists.shape[0] <= num_desc1
assert idxs.shape[1] == 2
assert idxs.shape[0] == dists.shape[0]
def test_matching(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
# rotate desc2 270 deg anti-clockwise
desc2 = desc2[:, [1, 0]]
desc2[:, 0] = -desc2[:, 0]
generator = torch.tensor([[0.0, 1], [-1, 0]], device=device)
steerer = DiscreteSteerer(generator)
matcher = DescriptorMatcherWithSteerer(steerer=steerer, steerer_order=4, steer_mode="global", match_mode="mnn")
dists, idxs, num_rot = matcher(desc1, desc2)
expected_dists = torch.tensor([0, 0, 0.5, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
assert num_rot == 3
class TestMatchSteererLocal(BaseTester):
@pytest.mark.parametrize("num_desc1, num_desc2, dim", [(1, 4, 4), (2, 5, 128), (6, 2, 32)])
def test_shape(self, num_desc1, num_desc2, dim, device):
desc1 = torch.rand(num_desc1, dim, device=device)
generator = torch.rand(dim, dim, device=device)
steerer = DiscreteSteerer(generator)
desc2 = steerer(desc1)
desc2[:1] = steerer(desc2[:1])
matcher = DescriptorMatcherWithSteerer(steerer=steerer, steerer_order=3, steer_mode="local", match_mode="mnn")
dists, idxs, _num_rot = matcher(desc1, desc2)
assert dists.shape[1] == 1
assert idxs.shape == (dists.shape[0], 2)
assert dists.shape[0] == num_desc1
def test_matching(self, device):
desc1 = torch.tensor([[0, 0.0], [1, 1], [2, 2], [3, 3.0], [5, 5.0]], device=device)
desc2 = torch.tensor([[5, 5.0], [3, 3.0], [2.3, 2.4], [1, 1], [0, 0.0]], device=device)
# rotate second to last element of desc2 90 deg anti-clockwise
desc2[-2] = desc2[-2, [1, 0]]
desc2[-2, 1] = -desc2[-2, 1]
# rotate first two elements of desc2 270 deg anti-clockwise
desc2[:2] = desc2[:2, [1, 0]]
desc2[:2, 0] = -desc2[:2, 0]
generator = torch.tensor([[0.0, 1], [-1, 0]], device=device)
steerer = DiscreteSteerer(generator)
matcher = DescriptorMatcherWithSteerer(steerer=steerer, steerer_order=4, steer_mode="local", match_mode="mnn")
dists, idxs, num_rot = matcher(desc1, desc2)
expected_dists = torch.tensor([0, 0, 0.5, 0, 0], device=device).view(-1, 1)
expected_idx = torch.tensor([[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], device=device)
self.assert_close(dists, expected_dists)
self.assert_close(idxs, expected_idx)
assert num_rot is None