| """Tests for the multi-modal image encoder (``palimseste.vision``). |
| |
| Verifies: |
| - encode_image returns a valid HV of the right dimensionality |
| - similar images produce similar HVs (correlated) |
| - dissimilar images produce quasi-orthogonal HVs |
| - ImageMemory stores and retrieves by similarity |
| - query_by_image finds the most similar stored image |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| import pytest |
|
|
| from palimseste import hv |
| from palimseste.vision import ImageEncoder, ImageMemory, encode_image |
|
|
|
|
| def _solid_image(color: tuple[int, int, int], size: int = 64) -> np.ndarray: |
| """A solid-color image.""" |
| img = np.zeros((size, size, 3), dtype=np.uint8) |
| img[:] = color |
| return img |
|
|
|
|
| def _random_image(seed: int, size: int = 64) -> np.ndarray: |
| rng = np.random.default_rng(seed) |
| return rng.integers(0, 256, size=(size, size, 3), dtype=np.uint8) |
|
|
|
|
| class TestImageEncoder: |
| def test_encode_returns_valid_hv(self): |
| enc = ImageEncoder(D=2000, grid=8) |
| img = _random_image(0) |
| h = enc.encode(img) |
| assert isinstance(h, hv.HV) |
| assert h.D == 2000 |
|
|
| def test_similar_images_are_similar(self): |
| enc = ImageEncoder(D=5000, grid=8) |
| img1 = _solid_image((100, 50, 200)) |
| img2 = _solid_image((105, 55, 195)) |
| h1 = enc.encode(img1) |
| h2 = enc.encode(img2) |
| sim = hv.similarity(h1, h2) |
| assert sim > 0.3, f"similar images should be correlated, got sim={sim}" |
|
|
| def test_dissimilar_images_are_quasi_orthogonal(self): |
| enc = ImageEncoder(D=5000, grid=8) |
| img1 = _solid_image((0, 0, 0)) |
| img2 = _solid_image((255, 255, 255)) |
| |
| |
| img3 = _random_image(1) |
| img4 = _random_image(2) |
| h3 = enc.encode(img3) |
| h4 = enc.encode(img4) |
| sim = hv.similarity(h3, h4) |
| |
| |
| assert sim < 0.5, f"random images should be weakly correlated, got sim={sim}" |
|
|
| def test_same_image_same_hv(self): |
| enc = ImageEncoder(D=3000, grid=8) |
| img = _random_image(42) |
| h1 = enc.encode(img) |
| h2 = enc.encode(img) |
| assert h1 == h2 |
|
|
| def test_encode_image_convenience(self): |
| img = _random_image(0, size=32) |
| h = encode_image(img, D=2000, grid=4) |
| assert h.D == 2000 |
|
|
| def test_invalid_shape_raises(self): |
| enc = ImageEncoder(D=1000) |
| with pytest.raises(ValueError): |
| enc.encode(np.zeros((10, 10), dtype=np.uint8)) |
|
|
| def test_small_image_works(self): |
| enc = ImageEncoder(D=2000, grid=8) |
| img = _random_image(0, size=4) |
| h = enc.encode(img) |
| assert h.D == 2000 |
|
|
|
|
| class TestImageMemory: |
| def test_store_and_retrieve(self): |
| mem = ImageMemory(D=3000) |
| img1 = _solid_image((255, 0, 0), size=32) |
| img2 = _solid_image((0, 255, 0), size=32) |
| mem.store(img1, label="red") |
| mem.store(img2, label="green") |
| results = mem.query_by_image(img1, top_k=1) |
| assert len(results) == 1 |
| idx, sim, label = results[0] |
| assert label == "red" |
| assert sim > 0.5 |
|
|
| def test_retrieve_top_k(self): |
| mem = ImageMemory(D=3000) |
| for i in range(10): |
| img = _solid_image((i * 25, 0, 0), size=32) |
| mem.store(img, label=f"color_{i}") |
| query = _solid_image((50, 0, 0), size=32) |
| results = mem.query_by_image(query, top_k=3) |
| assert len(results) == 3 |
| |
| assert results[0][1] >= results[1][1] >= results[2][1] |
|
|
| def test_empty_memory(self): |
| mem = ImageMemory(D=1000) |
| assert mem.retrieve(hv.random_hv(1000)) == [] |
|
|
| def test_size(self): |
| mem = ImageMemory(D=1000) |
| assert mem.size == 0 |
| mem.store(_random_image(0, size=16)) |
| assert mem.size == 1 |
|
|