"""The sensor export bundle: unit derivation, validation, and the flat view. The unit factor is the assertion that matters most. `export.py` writes vertices in the sensor frame *without* converting the design unit, and the conversion lives in `world_to_cam`'s linear block — so a consumer that assumes millimetres is wrong by ~54x, and everything downstream (platform coordinates, press depths) is wrong with it. These tests pin that down. The bundle used here is the bench-confirmed one. When it is not present the tests skip rather than fail: this suite has to run on machines that do not have the sensor-design checkout. """ from __future__ import annotations import numpy as np import pytest from orisys_autocal.sensor.export_bundle import ( BundleError, load_bundle, mm_per_unit_from_pose, ) from orisys_autocal.sensor.uv_flat import rasterise #: The bench-confirmed export, and the figures measured from it. BUNDLE = r"D:\orisys_ws\orisys-sensor-design\build\export\finger\r01\compute" EXPECTED_VERTICES = 1032 EXPECTED_TRIANGLES = 1974 EXPECTED_MM_PER_UNIT = 0.018388452316735537 EXPECTED_EXTENT_MM = (15.0, 7.5, 15.81) def _bundle(): if not __import__("pathlib").Path(BUNDLE).is_dir(): pytest.skip("the sensor-design export is not on this machine") return load_bundle( BUNDLE, expected_vertices=EXPECTED_VERTICES, expected_triangles=EXPECTED_TRIANGLES, ) @pytest.fixture(scope="module") def bundle(): return _bundle() # ------------------------------------------------------------------- the scale def test_the_unit_factor_is_derived_from_the_pose(bundle): assert bundle.mm_per_unit == pytest.approx(EXPECTED_MM_PER_UNIT, rel=1e-9) def test_the_physical_size_is_millimetres_not_mesh_units(bundle): """The failure this whole module exists to catch: a 54x unit error.""" extent = bundle.extent_mm assert extent[0] == pytest.approx(EXPECTED_EXTENT_MM[0], abs=0.02) assert extent[1] == pytest.approx(EXPECTED_EXTENT_MM[1], abs=0.02) assert extent[2] == pytest.approx(EXPECTED_EXTENT_MM[2], abs=0.02) def test_a_rigid_pose_means_one_millimetre_per_unit(): assert mm_per_unit_from_pose(np.eye(4)) == pytest.approx(1.0) def test_a_uniformly_scaled_pose_reports_that_scale(): pose = np.eye(4) pose[:3, :3] *= 0.5 assert mm_per_unit_from_pose(pose) == pytest.approx(0.5) def test_a_non_uniform_pose_is_refused(): """A per-axis scale cannot be undone by one factor; guessing is worse.""" pose = np.eye(4) pose[0, 0] = 0.01 pose[1, 1] = 0.02 pose[2, 2] = 0.03 with pytest.raises(BundleError) as excinfo: mm_per_unit_from_pose(pose) assert "uniform" in str(excinfo.value) def test_a_mirrored_pose_is_refused(): """A reflection would put every point on the wrong side of the sensor.""" pose = np.eye(4) pose[0, 0] = -1.0 with pytest.raises(BundleError) as excinfo: mm_per_unit_from_pose(pose) assert "mirror" in str(excinfo.value).lower() def test_a_degenerate_pose_is_refused(): with pytest.raises(BundleError): mm_per_unit_from_pose(np.zeros((4, 4))) # ------------------------------------------------------------------- the mesh def test_the_mesh_matches_the_design_profile(bundle): assert bundle.vertices_mm.shape == (EXPECTED_VERTICES, 3) assert bundle.faces.shape == (EXPECTED_TRIANGLES, 3) assert bundle.uv_data.shape[0] == EXPECTED_TRIANGLES * 3 assert bundle.loop_to_vertex.shape[0] == EXPECTED_TRIANGLES * 3 def test_every_face_index_is_in_range(bundle): assert int(bundle.faces.max()) < bundle.vertices_mm.shape[0] assert int(bundle.faces.min()) >= 0 assert int(bundle.loop_to_vertex.max()) < bundle.vertices_mm.shape[0] def test_the_surface_area_is_physically_plausible(bundle): """~356 mm^2 for a 15 x 7.5 x 15.8 mm patch: a unit error would show here.""" assert bundle.surface_area_mm2 == pytest.approx(355.77, abs=1.0) def test_the_stored_normals_are_coherent(bundle): """Mixed winding would make 'the normal' meaningless.""" normals = bundle.face_normals centroid = bundle.vertices_mm.mean(axis=0) centres = bundle.vertices_mm[bundle.faces].mean(axis=1) outward = np.einsum("ij,ij->i", normals, centres - centroid) assert float((outward > 0).mean()) > 0.95 def test_a_missing_file_is_named(tmp_dir): """A path that is not a directory at all says so, and names the path.""" absent = tmp_dir / "no-such-revision" with pytest.raises(BundleError) as excinfo: load_bundle(absent) assert "not a directory" in str(excinfo.value) assert absent.name in str(excinfo.value) def test_an_incomplete_bundle_names_what_is_missing(tmp_dir): (tmp_dir / "mesh.npz").write_bytes(b"") with pytest.raises(BundleError) as excinfo: load_bundle(tmp_dir) message = str(excinfo.value) assert "housing.npz" in message and "world_to_cam.npy" in message def test_a_count_mismatch_is_refused(bundle): with pytest.raises(BundleError) as excinfo: load_bundle(BUNDLE, expected_vertices=999) assert "999" in str(excinfo.value) assert str(EXPECTED_VERTICES) in str(excinfo.value) # --------------------------------------------------------------- the flat view @pytest.fixture(scope="module") def flat(bundle): return rasterise(bundle, (256, 256)) def test_the_flat_image_is_not_blank(flat): assert flat.image_rgb.shape == (256, 256, 3) assert flat.image_rgb.dtype == np.uint8 assert not bool((flat.image_rgb == 0).all()) def test_a_reasonable_share_of_the_image_carries_surface(flat): assert 0.5 < flat.valid_fraction < 0.9 def test_a_corner_pixel_is_an_island_gap(flat): """The atlas is a single island, so a corner is outside it.""" assert flat.sample(0, 0) is None assert flat.sample(flat.width - 1, flat.height - 1) is None def test_an_out_of_range_pixel_is_reported_not_crashed(flat): assert flat.sample(-1, 5) is None assert flat.sample(5, flat.height + 10) is None def test_every_covered_pixel_lies_on_its_recorded_face(bundle, flat): """The lookup table must be the mesh, not an approximation of it.""" covered = np.argwhere(flat.face_index >= 0) step = max(1, len(covered) // 300) worst = 0.0 for row, column in covered[::step]: face = int(flat.face_index[row, column]) triangle = bundle.vertices_mm[bundle.faces[face]] normal = np.cross(triangle[1] - triangle[0], triangle[2] - triangle[0]) normal = normal / np.linalg.norm(normal) distance = abs(float((flat.positions_mm[row, column] - triangle[0]) @ normal)) worst = max(worst, distance) assert worst < 1e-9 def test_every_covered_pixel_reports_its_face_normal(bundle, flat): covered = np.argwhere(flat.face_index >= 0) step = max(1, len(covered) // 300) for row, column in covered[::step]: face = int(flat.face_index[row, column]) assert float(flat.normals[row, column] @ bundle.face_normals[face]) == pytest.approx(1.0) def test_a_uv_corner_lands_on_a_pixel_near_its_vertex(bundle, flat): """The round trip: vertex -> its UV pixel -> looked-up 3D is within a pixel. Most corners land exactly on their vertex; a few land one pixel off a seam, which is what the tolerance is for. """ uv = bundle.uv_data.reshape(-1, 3, 2) worst = 0.0 misses = 0 for index in range(0, len(uv), 97): face, corner = divmod(index, 3) x, y = flat.pixel_for_uv(float(uv[face, corner, 0]), float(uv[face, corner, 1])) sample = flat.sample(x, y) if sample is None: # A corner exactly on an island boundary can miss; count it and keep # the check meaningful by requiring that it stays rare. misses += 1 continue vertex = bundle.vertices_mm[bundle.loop_to_vertex[index]] worst = max(worst, float(np.linalg.norm(sample[0] - vertex))) assert worst < 0.1, "a looked-up point is more than a pixel from its vertex" assert misses < len(range(0, len(uv), 97)) * 0.05