tests/test_objectives.py
import math
import torch
from mcss.losses import MeasurementLoss
from mcss.metrics import compute_metrics
def _targets() -> dict[str, torch.Tensor]:
return {
"rgb": torch.full((1, 1, 3, 4, 4), 0.5),
"depth": torch.full((1, 1, 1, 4, 4), 2.0),
"normal": torch.tensor([0.0, 0.0, 1.0]).view(1, 1, 3, 1, 1).expand(1, 1, 3, 4, 4),
"point": torch.zeros(1, 1, 3, 4, 4),
"visibility": torch.ones(1, 1, 1, 4, 4),
"uncertainty": torch.full((1, 1, 1, 4, 4), 0.1),
}
def test_perfect_predictions_have_zero_geometry_error() -> None:
targets = _targets()
metrics = compute_metrics(targets, targets)
assert math.isinf(metrics["rgb/psnr"])
assert metrics["depth/abs_rel"] == 0.0
assert metrics["depth/rmse"] == 0.0
assert metrics["depth/delta1"] == 1.0
assert metrics["normal/mean_angle"] < 0.03
assert metrics["visibility/iou"] == 1.0
assert metrics["visibility/f1"] == 1.0
def test_measurement_loss_masks_invalid_depth_and_is_differentiable() -> None:
targets = _targets()
targets["depth"][..., 0, 0] = 0.0
predictions = {name: value.clone().requires_grad_(True) for name, value in targets.items()}
predictions["depth"].data[..., 0, 0] = 100.0
objective = MeasurementLoss({"rgb": 1.0, "depth": 1.0, "normal": 1.0, "visibility": 1.0})
loss, terms = objective(predictions, targets)
assert terms["depth"] == 0.0
loss.backward()
assert predictions["depth"].grad is not None
def test_visibility_loss_is_safe_inside_cuda_autocast() -> None:
if not torch.cuda.is_available():
return
prediction = torch.full((1, 1, 1, 2, 2), 0.75, device="cuda", requires_grad=True)
target = torch.ones_like(prediction)
objective = MeasurementLoss({"visibility": 1.0})
with torch.autocast(device_type="cuda", enabled=True):
loss, terms = objective({"visibility": prediction}, {"visibility": target})
loss.backward()
assert torch.isfinite(loss)
assert torch.isfinite(terms["visibility"])
assert prediction.grad is not None
def test_support_mask_excludes_out_of_support_errors_from_every_loss() -> None:
targets = _targets()
support = torch.zeros_like(targets["visibility"])
support[..., 0, 0] = 1.0
targets["support"] = support
predictions = {name: value.clone() for name, value in targets.items() if name != "support"}
outside = ~support.bool()
predictions["rgb"] = torch.where(outside.expand_as(predictions["rgb"]), 0.0, predictions["rgb"])
predictions["depth"] = torch.where(outside, 100.0, predictions["depth"])
predictions["normal"] = torch.where(
outside.expand_as(predictions["normal"]),
-predictions["normal"],
predictions["normal"],
)
predictions["point"] = torch.where(
outside.expand_as(predictions["point"]), 100.0, predictions["point"]
)
predictions["visibility"] = torch.where(outside, 0.0, predictions["visibility"])
objective = MeasurementLoss(
{"rgb": 1.0, "depth": 1.0, "normal": 1.0, "point": 1.0, "visibility": 1.0}
)
baseline, _ = objective(
{name: target for name, target in targets.items() if name != "support"}, targets
)
loss, terms = objective(predictions, targets)
torch.testing.assert_close(loss, baseline)
assert terms["rgb"] < 0.002
assert terms["depth"] == 0.0
assert terms["normal"] == 0.0
assert terms["point"] == 0.0
def test_metrics_report_support_coverage_counts_and_metric_point_scales() -> None:
targets = _targets()
support = torch.zeros_like(targets["visibility"])
support[..., 0, 0] = 1.0
targets["support"] = support
predictions = {name: value.clone() for name, value in targets.items() if name != "support"}
outside = ~support.bool()
predictions["rgb"] = torch.where(outside.expand_as(predictions["rgb"]), 0.0, predictions["rgb"])
predictions["depth"] = torch.where(outside, 100.0, predictions["depth"])
predictions["point"] = torch.where(
outside.expand_as(predictions["point"]), 100.0, predictions["point"]
)
bounds = torch.tensor([[[-4.0, -3.0, 0.1], [4.0, 3.0, 8.1]]])
metrics = compute_metrics(predictions, targets, bounds=bounds)
assert metrics["support/coverage"] == 1.0 / 16.0
assert metrics["support/valid_pixels"] == 1.0
assert metrics["support/total_pixels"] == 16.0
assert metrics["depth/valid_pixels"] == 1.0
assert metrics["depth/rmse"] == 0.0
assert metrics["point/valid_points"] == 1.0
assert metrics["point/rmse_m"] == 0.0
assert metrics["point/nrmse_support_diag"] == 0.0
assert metrics["point/fscore_0.05"] == 1.0
assert metrics["point/fscore_0.25"] == 1.0
assert metrics["point/fscore_0.50"] == 1.0
def test_ssim_distinguishes_local_structure_with_identical_global_moments() -> None:
target = torch.full((1, 1, 3, 16, 16), 0.5)
y, x = torch.meshgrid(torch.arange(16), torch.arange(16), indexing="ij")
checkerboard = ((x + y) % 2).float()
split = torch.zeros(16, 16)
split[:8] = 1.0
checkerboard = checkerboard.view(1, 1, 1, 16, 16).expand_as(target)
split = split.view(1, 1, 1, 16, 16).expand_as(target)
torch.testing.assert_close(checkerboard.mean(), split.mean())
torch.testing.assert_close(checkerboard.var(unbiased=False), split.var(unbiased=False))
checkerboard_ssim = compute_metrics({"rgb": checkerboard}, {"rgb": target})["rgb/ssim"]
split_ssim = compute_metrics({"rgb": split}, {"rgb": target})["rgb/ssim"]
assert not math.isclose(checkerboard_ssim, split_ssim, abs_tol=1e-3)
def test_point_cloud_metrics_sample_errors_across_the_full_point_map() -> None:
target = torch.zeros(1, 1, 3, 64, 64)
prediction = target.clone()
prediction[:, :, 2, 32:, :] = 2.0
visibility = torch.ones(1, 1, 1, 64, 64)
metrics = compute_metrics(
{"point": prediction},
{"point": target, "visibility": visibility},
)
assert metrics["point/valid_points"] == 4096.0
assert metrics["point/chamfer_m2"] > 0.5
assert metrics["point/fscore_0.50"] < 0.9