#!/usr/bin/env python3 """Compare dora calibration with reference calibration.""" import argparse import numpy as np from scipy.spatial.transform import Rotation def load_calibration(path): """Load calibration from .npz file.""" data = np.load(path, allow_pickle=True) result = {"T_cam2gripper": data["T_cam2gripper"]} if "camera_matrix" in data: result["camera_matrix"] = data["camera_matrix"] if "distortion" in data: result["distortion"] = data["distortion"] return result def analyze_transform(T, name): """Print analysis of a 4x4 transformation matrix.""" print(f"\n=== {name} ===") print(f"Translation (mm): [{T[0,3]*1000:.2f}, {T[1,3]*1000:.2f}, {T[2,3]*1000:.2f}]") R = T[:3, :3] rpy = Rotation.from_matrix(R).as_euler('xyz', degrees=True) print(f"RPY (deg): [{rpy[0]:.2f}, {rpy[1]:.2f}, {rpy[2]:.2f}]") print(f"Full matrix:\n{T}") def analyze_camera(cam_matrix, name): """Print camera intrinsics analysis.""" print(f"\n=== {name} Camera Intrinsics ===") fx, fy = cam_matrix[0, 0], cam_matrix[1, 1] cx, cy = cam_matrix[0, 2], cam_matrix[1, 2] print(f"fx={fx:.2f}, fy={fy:.2f}, cx={cx:.2f}, cy={cy:.2f}") # Sanity checks issues = [] if fx < 100 or fy < 100: issues.append(f"Focal lengths too small (fx={fx:.1f}, fy={fy:.1f})") if cx < 100 or cy < 100: issues.append(f"Principal point too small (cx={cx:.1f}, cy={cy:.1f})") if abs(fx - fy) > 50: issues.append(f"Large focal length asymmetry: |fx-fy|={abs(fx-fy):.1f}") if issues: print("WARNINGS:") for issue in issues: print(f" - {issue}") else: print("Intrinsics appear valid") def main(): parser = argparse.ArgumentParser(description="Compare dora vs reference calibration.") parser.add_argument("--dora", default="calibration.npz", help="Path to dora calibration result") parser.add_argument("--reference", default="reference_calibration.npz", help="Path to reference calibration result") parser.add_argument("--check-intrinsics", action="store_true", help="Also compare camera intrinsics") args = parser.parse_args() try: dora_data = load_calibration(args.dora) dora = dora_data["T_cam2gripper"] except FileNotFoundError: print(f"Dora calibration not found: {args.dora}") return try: ref_data = load_calibration(args.reference) ref = ref_data["T_cam2gripper"] except FileNotFoundError: print(f"Reference calibration not found: {args.reference}") print("Analyzing dora calibration only:") analyze_transform(dora, "Dora Calibration") if args.check_intrinsics and "camera_matrix" in dora_data: analyze_camera(dora_data["camera_matrix"], "Dora") return analyze_transform(dora, "Dora Calibration") analyze_transform(ref, "Reference Calibration") # Check camera intrinsics if requested if args.check_intrinsics: if "camera_matrix" in dora_data: analyze_camera(dora_data["camera_matrix"], "Dora") if "camera_matrix" in ref_data: analyze_camera(ref_data["camera_matrix"], "Reference") # Compute differences print("\n=== DIFFERENCES ===") trans_diff = np.linalg.norm(dora[:3,3] - ref[:3,3]) * 1000 print(f"Translation difference: {trans_diff:.2f} mm") R_dora = dora[:3,:3] R_ref = ref[:3,:3] R_diff = R_dora @ R_ref.T angle_diff = np.rad2deg(np.arccos(np.clip((np.trace(R_diff) - 1) / 2, -1, 1))) print(f"Rotation difference: {angle_diff:.2f} deg") # Thresholds for acceptable calibration if trans_diff < 5.0 and angle_diff < 2.0: print("\n[OK] Calibrations are within acceptable tolerance") else: print("\n[FAIL] Calibrations differ significantly!") if trans_diff >= 5.0: print(f" Translation diff {trans_diff:.2f}mm exceeds 5mm threshold") if angle_diff >= 2.0: print(f" Rotation diff {angle_diff:.2f} deg exceeds 2 deg threshold") if __name__ == "__main__": main()