"""Evaluate point-cloud reprojection consistency against exported depth maps.""" from __future__ import annotations import argparse import json from pathlib import Path import numpy as np from evaluate_pointcloud import load_points from simam3d_core import reprojection_consistency def evaluate_reprojection(points_path: str | Path, metadata_path: str | Path) -> dict: points = load_points(points_path) with np.load(metadata_path) as metadata: required = {"depth", "intrinsics", "extrinsics"} missing = sorted(required.difference(metadata.files)) if missing: raise ValueError(f"metadata is missing required arrays: {', '.join(missing)}") depth = np.asarray(metadata["depth"]) intrinsics = np.asarray(metadata["intrinsics"]) extrinsics = np.asarray(metadata["extrinsics"]) if depth.ndim != 3: raise ValueError("depth metadata must have shape VxHxW") view_count = depth.shape[0] if intrinsics.shape != (view_count, 3, 3) or extrinsics.shape != (view_count, 4, 4): raise ValueError("camera metadata must match depth view count") views = [] for index in range(view_count): metrics = reprojection_consistency(points, depth[index], intrinsics[index], extrinsics[index]) views.append({"index": index, **metrics}) return { "mode": "reprojection_consistency_evaluation", "points": str(Path(points_path)), "metadata": str(Path(metadata_path)), "point_count": int(len(points)), "view_count": view_count, "mean_coverage": float(np.mean([view["coverage"] for view in views])), "mean_absolute_error": float(np.mean([view["mean_absolute_error"] for view in views])), "median_absolute_error": float(np.median([view["median_absolute_error"] for view in views])), "interpretation": "cross-view consistency diagnostic; not a geometric accuracy or hidden-surface claim", "views": views, } def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("points", type=Path) parser.add_argument("metadata", type=Path, help="run NPZ containing depth, intrinsics, and extrinsics") parser.add_argument("--output", type=Path) args = parser.parse_args(argv) report = evaluate_reprojection(args.points, args.metadata) destination = args.output if destination: destination.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") print(json.dumps(report, indent=2)) if destination: print(f"reprojection report: {destination}") return 0 if __name__ == "__main__": raise SystemExit(main())