
    def _rotation_vector_to_euler_angles(self, rvec):
        R, _ = cv2.Rodrigues(rvec)
        sy = math.sqrt(R[0, 0]**2 + R[1, 0]**2)
        singular = sy < 1e-6

        if not singular:
            x = math.atan2(R[2, 1], R[2, 2])
            y = math.atan2(-R[2, 0], sy)
            z = math.atan2(R[1, 0], R[0, 0])
        else:
            x = math.atan2(-R[1, 2], R[1, 1])
            y = math.atan2(-R[2, 0], sy)
            z = 0

        return (math.degrees(x), math.degrees(y), math.degrees(z))


    def _calculate_reprojection_error(self, obj_points, img_points, rvecs, tvecs):
        mean_error = 0
        for i in range(len(obj_points)):
            img_points2, _ = cv2.projectPoints(
                obj_points[i], rvecs[i], tvecs[i], self.camera_matrix, self.dist_coeffs)
            error = cv2.norm(img_points[i], img_points2, cv2.NORM_L2) / len(img_points2)
            mean_error += error
        return mean_error / len(obj_points)

 def auto_tilt_servo_alignment(self, servo_pin=1, neutral_angle=0, max_correction=90):
        if not self.tilt_angles:
            raise RuntimeError("No tilt angles available. Run calibrate_camera() first.")
        px = Picarx()
        pitch_angles = [angle[1] for angle in self.tilt_angles]
        avg_pitch = np.mean(pitch_angles)
        
        correction = -avg_pitch
        correction = np.clip(correction, -max_correction, max_correction)
        
        target_angle = np.clip(neutral_angle + correction, 0, 180)
        print(f"Moving tilt servo to {avg_pitch:.2f}° to correct pitch...")
        
        from gpiozero import AngularServo
        servo = AngularServo(servo_pin, min_angle=0, max_angle=180,
                             min_pulse_width=0.5/1000, max_pulse_width=2.5/1000)
        servo.angle = target_angle 
        px.set_cam_tilt_angle(target_angle)   
        time.sleep(0.5)
        px.stop()
        print("Tilt servo adjustment complete.")
        print(f"Adjusting pan servo to: {target_angle}")

        
    def auto_pan_servo_alignment(self, servo_pin=0, neutral_angle=0, max_correction=90):
        if not self.tilt_angles:
            raise RuntimeError("No tilt angles available. Run calibrate_camera() first.")
        px = Picarx()
        yaw_angles = [angle[2] for angle in self.tilt_angles]
        avg_yaw = np.mean(yaw_angles)

        
        correction = -avg_yaw
        correction = np.clip(correction, -max_correction, max_correction)
        
        target_angle = np.clip(neutral_angle + correction, 0, 180)
        print(f"Moving tilt servo to {target_angle:.4f}° to correct pitch...")
        
        from gpiozero import AngularServo
        servo = AngularServo(servo_pin, min_angle=0, max_angle=180,
                             min_pulse_width=0.5/1000, max_pulse_width=2.5/1000)
        servo.angle = target_angle
        px.set_cam_pan_angle(target_angle)
        time.sleep(0.5)
        px.stop()                     
        print("Pan servo adjustment complete.")
        print(f"Adjusting pan servo to: {target_angle}")
        
    def correct_roll_only(self, image):
        if not self.calibrated:
            raise RuntimeError("Camera not calibrated")

        if hasattr(self, 'avg_roll') and abs(self.avg_roll) > 0.5:
            h, w = image.shape[:2]
            center = (w//2, h//2)
            M = cv2.getRotationMatrix2D(center, angle = 90-self.avg_roll, scale = 1.0)
            image = cv2.warpAffine(image, M, (w,h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
        return image