115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
import numpy as np
|
|
import pytest
|
|
import trimesh
|
|
|
|
|
|
def _make_inputs():
|
|
image = np.zeros((64, 48, 3), dtype=np.float32)
|
|
mask = np.zeros((1, 64, 48), dtype=np.float32)
|
|
mask[0, 20:50, 10:38] = 1.0 # a blob
|
|
from cxn.types import Model3D
|
|
|
|
scene = trimesh.Scene(trimesh.creation.box(extents=(0.3, 0.1, 0.2)))
|
|
model = Model3D(scene=scene, source_path="shoe.glb", textures=[])
|
|
return model, image[None, ...], mask
|
|
|
|
|
|
def _patch_engine(monkeypatch):
|
|
import cxn.shoe_pose as sp
|
|
|
|
monkeypatch.setattr(
|
|
sp, "calibrate",
|
|
lambda img, use_gravity=True, fov_override=0.0: (47.0, np.array([0.0, 1.0, 0.0])),
|
|
)
|
|
captured = {}
|
|
|
|
def fake_solve(scene, target, init, **kw):
|
|
captured["scene"] = scene
|
|
captured["init"] = init
|
|
return dict(pitch=1.0, yaw=2.0, roll=3.0, cam_x=4.0, cam_y=5.0, cam_z=6.0)
|
|
|
|
monkeypatch.setattr(sp, "solve_pose", fake_solve)
|
|
return captured
|
|
|
|
|
|
def test_estimate_returns_eight_tuple_and_fov(monkeypatch):
|
|
_patch_engine(monkeypatch)
|
|
from cxn.shoe_pose import ShoePose
|
|
from cxn.types import Model3D
|
|
|
|
model, image, mask = _make_inputs()
|
|
out = ShoePose().estimate(model, image, mask, left_or_right=False)
|
|
assert len(out) == 8
|
|
assert isinstance(out[0], Model3D)
|
|
# (model, cam_x, cam_y, cam_z, pitch, yaw, roll, fov)
|
|
assert out[1:] == (4.0, 5.0, 6.0, 1.0, 2.0, 3.0, 47.0)
|
|
|
|
|
|
def test_left_foot_mirrors_scene(monkeypatch):
|
|
captured = _patch_engine(monkeypatch)
|
|
from cxn.shoe_pose import ShoePose
|
|
|
|
model, image, mask = _make_inputs()
|
|
n_before = len(model.scene.geometry)
|
|
ShoePose().estimate(model, image, mask, left_or_right=True)
|
|
# _mirror_shoe rebuilds geometry; the scene handed to solve_pose is a new one.
|
|
assert captured["scene"] is not model.scene
|
|
assert len(captured["scene"].geometry) == n_before
|
|
|
|
|
|
def test_return_signature_matches_matchfootpose():
|
|
from cxn.shoe_pose import ShoePose
|
|
from cxn.match_foot_pose import MatchFootPose
|
|
|
|
assert ShoePose.RETURN_TYPES == MatchFootPose.RETURN_TYPES
|
|
assert ShoePose.RETURN_NAMES == MatchFootPose.RETURN_NAMES
|
|
|
|
|
|
def test_empty_mask_raises(monkeypatch):
|
|
_patch_engine(monkeypatch)
|
|
from cxn.shoe_pose import ShoePose
|
|
|
|
model, image, _mask = _make_inputs()
|
|
empty = np.zeros((1, 64, 48), dtype=np.float32)
|
|
with pytest.raises(RuntimeError):
|
|
ShoePose().estimate(model, image, empty, left_or_right=False)
|
|
|
|
|
|
def test_initial_pose_centers_camera_offset_for_offcenter_mask():
|
|
from cxn.shoe_pose import _initial_pose
|
|
|
|
m = np.zeros((100, 100), dtype=bool)
|
|
m[10:30, 10:30] = True # top-left blob
|
|
init = _initial_pose(
|
|
m, np.array([0.0, 1.0, 0.0]), 45.0, (100, 100),
|
|
model_extent_m=0.3, depth_cz=None,
|
|
)
|
|
# Object is top-left (cx<center, cy<center): cam_x>0 (offset right), cam_y<0.
|
|
assert init["cam_x"] > 0
|
|
assert init["cam_y"] < 0
|
|
assert init["cam_z"] > 0
|
|
|
|
|
|
def test_initial_pose_uses_depth_cz_when_given():
|
|
from cxn.shoe_pose import _initial_pose
|
|
|
|
m = np.zeros((100, 100), dtype=bool)
|
|
m[40:60, 40:60] = True
|
|
init = _initial_pose(
|
|
m, np.array([0.0, 1.0, 0.0]), 45.0, (100, 100),
|
|
model_extent_m=0.3, depth_cz=1.234,
|
|
)
|
|
assert abs(init["cam_z"] - 1.234) < 1e-6
|
|
|
|
|
|
def test_image_and_mask_conversions():
|
|
from cxn.shoe_pose import image_to_rgb_uint8, mask_to_bool
|
|
|
|
img = np.ones((1, 8, 8, 3), dtype=np.float32) * 0.5
|
|
out = image_to_rgb_uint8(img)
|
|
assert out.shape == (8, 8, 3) and out.dtype == np.uint8 and out[0, 0, 0] == 128
|
|
mask = np.zeros((1, 8, 8), dtype=np.float32)
|
|
mask[0, 0, 0] = 1.0
|
|
mb = mask_to_bool(mask)
|
|
assert mb.shape == (8, 8) and mb[0, 0] and not mb[1, 1]
|