added motion simulator feature
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user