#!/usr/bin/env python3 """Analyze saved observations for issues.""" import argparse import numpy as np from scipy.spatial.transform import Rotation def main(): parser = argparse.ArgumentParser(description="Analyze hand-eye observations for issues.") parser.add_argument("observations", nargs="?", default="calibration_observations.npz", help="Path to observations .npz file") args = parser.parse_args() # Load dora observations dora_obs = np.load(args.observations, allow_pickle=True) print("=== OBSERVATIONS ANALYSIS ===") print(f"File: {args.observations}") print(f"Keys: {list(dora_obs.keys())}") R_gripper2base = dora_obs['R_gripper2base'] t_gripper2base = dora_obs['t_gripper2base'] R_target2cam = dora_obs['R_target2cam'] t_target2cam = dora_obs['t_target2cam'] n = len(R_gripper2base) print(f"Count: {n}") print("\n=== GRIPPER POSES (base_T_gripper) ===") for i in range(n): R = R_gripper2base[i] t = t_gripper2base[i].flatten() # Check rotation matrix validity det = np.linalg.det(R) orth = np.allclose(R @ R.T, np.eye(3), atol=1e-6) rpy = Rotation.from_matrix(R).as_euler('xyz', degrees=True) print(f"\nObs {i+1}:") print(f" det(R)={det:.6f}, orthogonal={orth}") print(f" t(mm)=[{t[0]*1000:.1f}, {t[1]*1000:.1f}, {t[2]*1000:.1f}]") print(f" RPY(deg)=[{rpy[0]:.1f}, {rpy[1]:.1f}, {rpy[2]:.1f}]") if not orth: print(" WARNING: Rotation matrix not orthogonal!") if abs(det - 1.0) > 1e-6: print(f" WARNING: Rotation matrix determinant is {det}, expected 1.0!") print("\n=== TARGET POSES (cam_T_target) ===") for i in range(n): R = R_target2cam[i] t = t_target2cam[i].flatten() det = np.linalg.det(R) orth = np.allclose(R @ R.T, np.eye(3), atol=1e-6) print(f"\nObs {i+1}:") print(f" det(R)={det:.6f}, orthogonal={orth}") print(f" t(m)=[{t[0]:.4f}, {t[1]:.4f}, {t[2]:.4f}]") print(f" distance from camera: {np.linalg.norm(t)*1000:.1f}mm") # Check for pose diversity (important for calibration) print("\n=== POSE DIVERSITY CHECK ===") positions = np.array([t_gripper2base[i].flatten() for i in range(n)]) pos_range = positions.max(axis=0) - positions.min(axis=0) print(f"Position range (mm): X={pos_range[0]*1000:.1f}, Y={pos_range[1]*1000:.1f}, Z={pos_range[2]*1000:.1f}") rotations = np.array([Rotation.from_matrix(R_gripper2base[i]).as_euler('xyz', degrees=True) for i in range(n)]) rot_range = rotations.max(axis=0) - rotations.min(axis=0) print(f"Rotation range (deg): Roll={rot_range[0]:.1f}, Pitch={rot_range[1]:.1f}, Yaw={rot_range[2]:.1f}") if __name__ == "__main__": main()