"""The UV-space texture: loading it, resampling it, and falling back without it. The texture is authored in UV coordinates, black outside the island, so it resamples into exactly the space the picker already uses. These tests pin the two things that would silently ruin that: the orientation of the resample, and the claim that adding a texture cannot change where a click lands. """ from __future__ import annotations from pathlib import Path import numpy as np import pytest from orisys_autocal.sensor import ( DEFAULT_FLAT_SIZE, load_bundle, load_uv_texture, rasterise, texture_path_for, ) BUNDLE = Path(r"D:\orisys_ws\orisys-sensor-design\build\export\finger\r01\compute") pytestmark = pytest.mark.skipif( not BUNDLE.is_dir(), reason=f"no sensor export at {BUNDLE}" ) @pytest.fixture(scope="module") def bundle(): return load_bundle(BUNDLE) @pytest.fixture(scope="module") def texture(bundle): path = texture_path_for(bundle) if path is None: pytest.skip("this export has no texture beside it") loaded = load_uv_texture(path) if loaded is None: pytest.skip(f"could not decode {path}") return loaded def test_the_texture_is_found_from_the_bundle(bundle): """Derived, not configured: a mismatched pair resamples the wrong artwork.""" path = texture_path_for(bundle) if path is None: pytest.skip("this export has no texture beside it") assert path.is_file() assert path.name == "patch_texture_compute.png" assert path.parent.parent.name == "finger" def test_loading_a_texture_returns_an_rgb_ready_array(texture): assert texture.ndim == 3 and texture.shape[2] == 4 assert texture.dtype == np.uint8 assert texture.shape[0] > 1 and texture.shape[1] > 1 def test_a_missing_texture_is_none_not_an_exception(): """The picker has to keep working without artwork.""" assert load_uv_texture(r"D:\definitely\not\here.png") is None def test_the_texture_is_black_outside_the_island_and_not_inside(texture): """The island's v extent is where the artwork lives; above and below is black.""" height = texture.shape[0] top = texture[: int(height * 0.05), :, :3] bottom = texture[int(height * 0.95) :, :, :3] middle = texture[int(height * 0.45) : int(height * 0.55), :, :3] assert int(top.max()) == 0, "the top band should be unused black" assert int(bottom.max()) == 0, "the bottom band should be unused black" assert int(middle.max()) > 0, "the island band should carry content" def test_a_texture_does_not_change_where_a_click_lands(bundle, texture): """The lookup tables are the measurement; the image is only what you aim at.""" size = (256, 256) plain = rasterise(bundle, size) textured = rasterise(bundle, size, texture=texture) assert plain.textured is False assert textured.textured is True assert np.array_equal(plain.face_index, textured.face_index) assert np.allclose(plain.positions_mm, textured.positions_mm) assert np.allclose(plain.normals, textured.normals) assert plain.uv_min == textured.uv_min and plain.uv_max == textured.uv_max def test_the_textured_image_samples_the_texture_in_the_right_place(bundle, texture): """Resample at the pixel's own UV, including the flip, or the map is mirrored.""" textured = rasterise(bundle, (256, 256), texture=texture) covered = np.nonzero(textured.face_index >= 0) rows, columns = covered[0][::97], covered[1][::97] assert rows.size > 20 height, width = textured.face_index.shape u0, v0 = textured.uv_min u1, v1 = textured.uv_max texture_height, texture_width = texture.shape[0], texture.shape[1] for row, column in zip(rows, columns): u = u0 + column / (width - 1) * (u1 - u0) v = v1 - row / (height - 1) * (v1 - v0) tex_x = int(round(u * (texture_width - 1))) tex_y = int(round((1.0 - v) * (texture_height - 1))) expected = texture[tex_y, tex_x, :3] assert np.array_equal(textured.image_rgb[row, column], expected), ( f"pixel ({column}, {row}) does not carry the texture sample at its own UV" ) def test_gap_pixels_stay_black_when_textured(bundle, texture): """A fabricated colour in the gap would look like part of the patch.""" textured = rasterise(bundle, (256, 256), texture=texture) gaps = textured.face_index < 0 if not gaps.any(): pytest.skip("this raster has no gap pixels") assert int(textured.image_rgb[gaps].max()) == 0 def test_the_default_flat_size_is_still_the_design_size(): assert DEFAULT_FLAT_SIZE == (1024, 1024)