first commit
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
45
tests/test_curve_utils.py
Normal file
45
tests/test_curve_utils.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import numpy as np
|
||||
|
||||
from utils.curve import compute_letterbox, sample_lane_at_ys
|
||||
from utils.mask_to_lanes import mask_to_lanes
|
||||
|
||||
|
||||
def test_letterbox_roundtrip():
|
||||
lb = compute_letterbox(src_w=1280, src_h=720, out_w=640, out_h=360)
|
||||
pts = np.array([[100.0, 200.0], [1000.0, 500.0]], dtype=np.float32)
|
||||
warped = lb.apply_points(pts)
|
||||
restored = lb.invert_points(warped)
|
||||
assert np.allclose(pts, restored, atol=1e-3)
|
||||
|
||||
|
||||
def test_letterbox_stays_in_bounds():
|
||||
lb = compute_letterbox(src_w=1280, src_h=720, out_w=640, out_h=360)
|
||||
corners = np.array([[0, 0], [1279, 719]], dtype=np.float32)
|
||||
warped = lb.apply_points(corners)
|
||||
assert (warped[:, 0] >= -1).all() and (warped[:, 0] <= 641).all()
|
||||
assert (warped[:, 1] >= -1).all() and (warped[:, 1] <= 361).all()
|
||||
|
||||
|
||||
def test_sample_lane_at_ys_interpolates_and_masks():
|
||||
lane = [(0.0, 0.0), (10.0, 1.0)] # x = 10*y
|
||||
sample_ys = np.array([0.0, 0.5, 1.0, 1.5], dtype=np.float32)
|
||||
xs, valid = sample_lane_at_ys(lane, sample_ys)
|
||||
assert valid.tolist() == [True, True, True, False]
|
||||
assert np.allclose(xs[:3], [0.0, 5.0, 10.0], atol=1e-4)
|
||||
|
||||
|
||||
def test_mask_to_lanes_separates_instances():
|
||||
mask = np.zeros((100, 200), dtype=np.uint8)
|
||||
mask[:, 20:24] = 255 # vertical strip -> one lane instance
|
||||
mask[:, 150:154] = 255 # another, far apart -> separate instance
|
||||
lanes = mask_to_lanes(mask)
|
||||
assert len(lanes) == 2
|
||||
for lane in lanes:
|
||||
assert len(lane) >= 5
|
||||
|
||||
|
||||
def test_mask_to_lanes_drops_small_noise_blobs():
|
||||
mask = np.zeros((100, 200), dtype=np.uint8)
|
||||
mask[10:12, 10:12] = 255 # tiny 2x2 JPEG-noise-like blob, below MIN_BLOB_AREA
|
||||
lanes = mask_to_lanes(mask)
|
||||
assert lanes == []
|
||||
54
tests/test_matcher.py
Normal file
54
tests/test_matcher.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import torch
|
||||
|
||||
from models.matcher import hungarian_match
|
||||
|
||||
|
||||
def test_matcher_recovers_perfect_assignment():
|
||||
"""If predicted curves exactly equal GT curves (in different query order),
|
||||
the matcher should recover the correct pred<->gt pairing."""
|
||||
torch.manual_seed(0)
|
||||
B, N, S = 1, 4, 10
|
||||
sample_ys = torch.linspace(0, 1, S)
|
||||
|
||||
gt_xs = torch.rand(B, N, S)
|
||||
target_valid_mask = torch.ones(B, N, S, dtype=torch.bool)
|
||||
target_lane_valid = torch.tensor([[True, True, False, False]])
|
||||
|
||||
# Build predictions as a *permuted* copy of the (valid) GT curves via exact
|
||||
# per-point fit isn't needed here -- construct predicted curve coeffs that
|
||||
# exactly reproduce gt_xs at the two valid GT rows, placed at query indices [2, 0].
|
||||
curve_coeffs = torch.zeros(B, N, 4)
|
||||
cls_logits = torch.zeros(B, N, 2)
|
||||
|
||||
for q_idx, gt_idx in [(2, 0), (0, 1)]:
|
||||
# fit a cubic through the GT points exactly (deg-3 fit on 10 pts, small residual)
|
||||
ys_np = sample_ys.numpy()
|
||||
xs_np = gt_xs[0, gt_idx].numpy()
|
||||
coeffs = torch.tensor([0.0, 0.0, 0.0, 0.0])
|
||||
import numpy as np
|
||||
fit = np.polyfit(ys_np, xs_np, deg=3)
|
||||
coeffs = torch.tensor(fit, dtype=torch.float32)
|
||||
curve_coeffs[0, q_idx] = coeffs
|
||||
cls_logits[0, q_idx] = torch.tensor([-5.0, 5.0]) # confident "lane"
|
||||
|
||||
matches = hungarian_match(cls_logits, curve_coeffs, gt_xs, target_valid_mask,
|
||||
target_lane_valid, sample_ys)
|
||||
pred_idx, gt_idx = matches[0]
|
||||
pairs = set(zip(pred_idx.tolist(), gt_idx.tolist()))
|
||||
assert (2, 0) in pairs
|
||||
assert (0, 1) in pairs
|
||||
|
||||
|
||||
def test_matcher_handles_no_gt_lanes():
|
||||
B, N, S = 1, 4, 10
|
||||
sample_ys = torch.linspace(0, 1, S)
|
||||
cls_logits = torch.zeros(B, N, 2)
|
||||
curve_coeffs = torch.zeros(B, N, 4)
|
||||
target_xs = torch.zeros(B, N, S)
|
||||
target_valid_mask = torch.zeros(B, N, S, dtype=torch.bool)
|
||||
target_lane_valid = torch.zeros(B, N, dtype=torch.bool)
|
||||
|
||||
matches = hungarian_match(cls_logits, curve_coeffs, target_xs, target_valid_mask,
|
||||
target_lane_valid, sample_ys)
|
||||
pred_idx, gt_idx = matches[0]
|
||||
assert pred_idx.numel() == 0 and gt_idx.numel() == 0
|
||||
27
tests/test_model_forward.py
Normal file
27
tests/test_model_forward.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import torch
|
||||
|
||||
from models.laneformer import LaneFormer
|
||||
|
||||
|
||||
def test_forward_shapes():
|
||||
torch.manual_seed(0)
|
||||
model = LaneFormer(pretrained=False, max_lanes=8)
|
||||
x = torch.randn(2, 3, 360, 640)
|
||||
out = model(x)
|
||||
assert out["cls_logits"].shape == (2, 8, 2)
|
||||
assert out["curve_coeffs"].shape == (2, 8, 4)
|
||||
assert out["endpoints"].shape == (2, 8, 2)
|
||||
assert torch.isfinite(out["cls_logits"]).all()
|
||||
assert torch.isfinite(out["curve_coeffs"]).all()
|
||||
assert (out["endpoints"] >= 0).all() and (out["endpoints"] <= 1).all()
|
||||
|
||||
|
||||
def test_backward_runs():
|
||||
torch.manual_seed(0)
|
||||
model = LaneFormer(pretrained=False, max_lanes=8)
|
||||
x = torch.randn(1, 3, 360, 640)
|
||||
out = model(x)
|
||||
loss = out["cls_logits"].sum() + out["curve_coeffs"].sum() + out["endpoints"].sum()
|
||||
loss.backward()
|
||||
grads = [p.grad for p in model.parameters() if p.requires_grad]
|
||||
assert any(g is not None and torch.isfinite(g).all() for g in grads)
|
||||
Reference in New Issue
Block a user