Spaces:
Sleeping
Sleeping
| """ | |
| Tests for multi-modal retrieval. | |
| These tests verify: | |
| - Same-modal retrieval (optical→optical, SAR→SAR, multispectral→multispectral) | |
| - Cross-modal retrieval (optical↔SAR, optical↔multispectral) | |
| - Modality filtering correctness | |
| - Modality distribution tracking | |
| """ | |
| import pytest | |
| import torch | |
| import numpy as np | |
| from src.retrieval.multimodal import MultiModalRetrieval, ModalityResult | |
| def multimodal_embeddings(): | |
| """Create L2-normalized embeddings for all modalities.""" | |
| n_per_modality = 50 | |
| embed_dim = 768 | |
| embeddings = { | |
| "optical": torch.randn(n_per_modality, embed_dim), | |
| "sar": torch.randn(n_per_modality, embed_dim), | |
| "multispectral": torch.randn(n_per_modality, embed_dim), | |
| } | |
| # L2 normalize | |
| for mod in embeddings: | |
| embeddings[mod] = torch.nn.functional.normalize(embeddings[mod], dim=1) | |
| return embeddings | |
| def built_retrieval(multimodal_embeddings): | |
| """Create a MultiModalRetrieval with embeddings loaded.""" | |
| retrieval = MultiModalRetrieval(embed_dim=768) | |
| retrieval.build_index(multimodal_embeddings) | |
| return retrieval | |
| def dummy_query(): | |
| """Create a dummy query embedding.""" | |
| query = torch.randn(768) | |
| return torch.nn.functional.normalize(query, dim=0) | |
| class TestMultiModalRetrieval: | |
| """Tests for MultiModalRetrieval class.""" | |
| def test_initialization(self): | |
| """Test initialization.""" | |
| retrieval = MultiModalRetrieval(embed_dim=512) | |
| assert retrieval.embed_dim == 512 | |
| assert retrieval.size == 0 | |
| def test_build_index(self, multimodal_embeddings): | |
| """Test index builds correctly.""" | |
| retrieval = MultiModalRetrieval(embed_dim=768) | |
| retrieval.build_index(multimodal_embeddings) | |
| assert retrieval.size == 150 # 50 * 3 modalities | |
| assert len(retrieval.modality_labels) == 150 | |
| def test_modality_distribution(self, built_retrieval): | |
| """Test modality distribution tracking.""" | |
| dist = built_retrieval.get_modality_distribution() | |
| assert dist["optical"] == 50 | |
| assert dist["sar"] == 50 | |
| assert dist["multispectral"] == 50 | |
| def test_same_modal_optical(self, built_retrieval, dummy_query): | |
| """Test same-modal optical→optical retrieval.""" | |
| result = built_retrieval.same_modal_query( | |
| dummy_query, modality="optical", k=5 | |
| ) | |
| assert isinstance(result, ModalityResult) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "optical" for m in result.modalities) | |
| assert result.query_modality == "optical" | |
| def test_same_modal_sar(self, built_retrieval, dummy_query): | |
| """Test same-modal SAR→SAR retrieval.""" | |
| result = built_retrieval.same_modal_query( | |
| dummy_query, modality="sar", k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "sar" for m in result.modalities) | |
| assert result.query_modality == "sar" | |
| def test_same_modal_multispectral(self, built_retrieval, dummy_query): | |
| """Test same-modal multispectral→multispectral retrieval.""" | |
| result = built_retrieval.same_modal_query( | |
| dummy_query, modality="multispectral", k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "multispectral" for m in result.modalities) | |
| assert result.query_modality == "multispectral" | |
| def test_cross_modal_optical_to_sar(self, built_retrieval, dummy_query): | |
| """Test cross-modal optical→SAR retrieval.""" | |
| result = built_retrieval.cross_modal_query( | |
| dummy_query, | |
| source_modality="optical", | |
| target_modality="sar", | |
| k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "sar" for m in result.modalities) | |
| assert result.query_modality == "optical" | |
| def test_cross_modal_optical_to_multispectral(self, built_retrieval, dummy_query): | |
| """Test cross-modal optical→multispectral retrieval.""" | |
| result = built_retrieval.cross_modal_query( | |
| dummy_query, | |
| source_modality="optical", | |
| target_modality="multispectral", | |
| k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "multispectral" for m in result.modalities) | |
| def test_cross_modal_sar_to_optical(self, built_retrieval, dummy_query): | |
| """Test cross-modal SAR→optical retrieval.""" | |
| result = built_retrieval.cross_modal_query( | |
| dummy_query, | |
| source_modality="sar", | |
| target_modality="optical", | |
| k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "optical" for m in result.modalities) | |
| def test_cross_modal_sar_to_multispectral(self, built_retrieval, dummy_query): | |
| """Test cross-modal SAR→multispectral retrieval.""" | |
| result = built_retrieval.cross_modal_query( | |
| dummy_query, | |
| source_modality="sar", | |
| target_modality="multispectral", | |
| k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "multispectral" for m in result.modalities) | |
| def test_cross_modal_multispectral_to_optical(self, built_retrieval, dummy_query): | |
| """Test cross-modal multispectral→optical retrieval.""" | |
| result = built_retrieval.cross_modal_query( | |
| dummy_query, | |
| source_modality="multispectral", | |
| target_modality="optical", | |
| k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "optical" for m in result.modalities) | |
| def test_cross_modal_multispectral_to_sar(self, built_retrieval, dummy_query): | |
| """Test cross-modal multispectral→SAR retrieval.""" | |
| result = built_retrieval.cross_modal_query( | |
| dummy_query, | |
| source_modality="multispectral", | |
| target_modality="sar", | |
| k=5 | |
| ) | |
| assert len(result.indices) <= 5 | |
| assert all(m == "sar" for m in result.modalities) | |
| def test_mixed_query(self, built_retrieval, dummy_query): | |
| """Test mixed query returns all modalities.""" | |
| result = built_retrieval.mixed_query( | |
| dummy_query, source_modality="optical", k=10 | |
| ) | |
| assert len(result.indices) <= 10 | |
| assert len(result.modalities) == len(result.indices) | |
| def test_scores_valid(self, built_retrieval, dummy_query): | |
| """Test similarity scores are in valid range.""" | |
| result = built_retrieval.same_modal_query( | |
| dummy_query, modality="optical", k=5 | |
| ) | |
| for score in result.scores: | |
| assert -1.0 <= score <= 1.0 | |
| def test_indices_valid(self, built_retrieval, dummy_query): | |
| """Test returned indices are valid.""" | |
| result = built_retrieval.same_modal_query( | |
| dummy_query, modality="optical", k=5 | |
| ) | |
| for idx in result.indices: | |
| assert 0 <= idx < built_retrieval.size | |
| class TestModalityResult: | |
| """Tests for ModalityResult dataclass.""" | |
| def test_modality_result_structure(self): | |
| """Test ModalityResult has correct fields.""" | |
| result = ModalityResult( | |
| indices=[0, 1, 2], | |
| scores=[0.9, 0.8, 0.7], | |
| modalities=["optical", "optical", "optical"], | |
| query_modality="optical" | |
| ) | |
| assert result.indices == [0, 1, 2] | |
| assert result.scores == [0.9, 0.8, 0.7] | |
| assert result.modalities == ["optical", "optical", "optical"] | |
| assert result.query_modality == "optical" | |
| # Self-check | |
| if __name__ == "__main__": | |
| pytest.main([__file__, "-v"]) | |