painting-vision-robotics-kit / test_depth_validation.py
constructelligence's picture
Upload test_depth_validation.py with huggingface_hub
56b261b verified
Raw History Blame Contribute Delete
3.84 kB
#!/usr/bin/env python3
"""Regression tests for depth_validation using synthetic depth.
No real LiDAR is required: we render a fronto-parallel wall plane with a
recessed window and a protruding pipe and check the module recovers the plane,
the metric scale, and both features. Run with ``python3 test_depth_validation.py``.
"""
import numpy as np
import depth_validation as dv
FX = FY = 500.0
CX = CY = 160.0
WALL_Z = 3.0
def synthetic_scene(height=320, width=320, noise=0.002, seed=1):
rng = np.random.default_rng(seed)
depth = np.full((height, width), WALL_Z, dtype=np.float32)
window = np.zeros((height, width), bool)
window[90:150, 60:120] = True # recessed: farther from camera
depth[window] = WALL_Z + 0.20
pipe = np.zeros((height, width), bool)
pipe[200:280, 250:262] = True # protruding: closer to camera
depth[pipe] = WALL_Z - 0.15
depth += rng.normal(0, noise, depth.shape).astype(np.float32)
return depth, window, pipe
def test_unproject_center_ray():
depth = np.full((11, 11), 3.0, dtype=np.float32)
points = dv.unproject(depth, FX, FY, 5.0, 5.0)
assert np.allclose(points[5, 5], [0.0, 0.0, 3.0], atol=1e-6), points[5, 5]
# One pixel to the right of the principal point is fx-normalised in X.
assert abs(points[5, 6, 0] - 3.0 / FX) < 1e-6
def test_fit_plane_recovers_wall():
depth, _, _ = synthetic_scene()
points = dv.unproject(depth, FX, FY, CX, CY)
valid = np.isfinite(points).all(-1)
normal, offset, inliers = dv.fit_plane(points[valid], distance_threshold=0.01)
assert abs(abs(normal[2]) - 1.0) < 1e-3, normal
assert abs(offset + WALL_Z) < 0.02, offset
assert inliers.mean() > 0.9
def test_residual_sign_separates_protrusion_from_recess():
depth, window, pipe = synthetic_scene()
points = dv.unproject(depth, FX, FY, CX, CY)
valid = np.isfinite(points).all(-1)
normal, offset, _ = dv.fit_plane(points[valid], distance_threshold=0.01)
residual = dv.plane_residuals(points, normal, offset)
assert residual[window].mean() > 0.15, "window recess should be positive (farther)"
assert residual[pipe].mean() < -0.10, "pipe protrusion should be negative (closer)"
def test_validate_flags_missed_features():
depth, window, pipe = synthetic_scene()
wall = np.ones_like(depth, bool)
# Keep-out covers the window but not the pipe: the pipe must be flagged.
keepout = window.copy()
report, layers = dv.validate(depth, (FX, FY, CX, CY), wall_mask=wall, window_mask=window,
keepout_mask=keepout)
assert report["plane"]["inlier_fraction"] > 0.9
assert report["recess_vs_window_iou"] > 0.8
assert report["missed_protrusions_pixels"] > 500
assert any("protrusion" in w for w in report["warnings"])
assert layers["recesses"][130, 90] and layers["protrusions"][240, 256]
def test_metric_scale_is_physical():
depth, _, _ = synthetic_scene(noise=0.0)
points = dv.unproject(depth, FX, FY, CX, CY)
scale = dv.metric_scale_mm_per_pixel(points, FX, FY)
# 3 m at fx=500 -> 6 mm per pixel.
assert abs(scale["x"] - 6.0) < 0.05, scale
assert abs(scale["y"] - 6.0) < 0.05, scale
def test_non_planar_wall_warns():
rng = np.random.default_rng(3)
depth = (WALL_Z + rng.normal(0, 0.3, (320, 320))).astype(np.float32) # rough, non-planar
wall = np.ones_like(depth, bool)
report, _ = dv.validate(depth, (FX, FY, CX, CY), wall_mask=wall)
assert report["wall_fraction_on_plane"] < 0.9
assert any("plan" in w for w in report["warnings"])
def main():
tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")]
for test in tests:
test()
print(f"ok {test.__name__}")
print(f"{len(tests)} tests passed")
if __name__ == "__main__":
main()