s6.app.utils.rim_solve¶
Pose from a single projected circle on a gimbal-mounted plane (no roll). - Known: intrinsics K, image size, pivot distance (base_Z), stick length l, circle radius R. - Unknown: tilt (phi, theta). Estimate from ellipse fitted to observed ring edges. Theta is the direction at which the assembly tilts.
- Solution A:
Fit ellipse once (direct LS) -> conic C_obs
- Closed-form-ish init:
phi0 ≈ arccos(b/a) theta0 ≈ alpha ± 90° (choose better)
Tiny Gauss–Newton refinement (3–4 steps) minimizing algebraic conic residuals of projected circle points against C_obs using the exact gimbal model.
Author: you + your chaotic AI bestie ✨
- s6.app.utils.rim_solve.intrinsics_from_hfov(width_px: int, height_px: int, hfov_deg: float) ndarray
Build pinhole intrinsics from horizontal FOV (degrees) and resolution. Square pixels, principal point at center.
- s6.app.utils.rim_solve.rot_no_roll(phi_deg: float, theta_deg: float) tuple[ndarray, ndarray]
Rotation that tilts +Z by phi toward azimuth theta WITHOUT roll about the new normal. Returns (R, d) where d = R*[0,0,1] (the new normal).
- s6.app.utils.rim_solve.project_circle_points(K: ndarray, base_Z_mm: float, stick_l_mm: float, radius_mm: float, phi_deg: float, theta_deg: float, num_samples: int = 64) ndarray
Project points from the true 3D circle on the tilted/translated plane. Returns (N,2) pixel coords.
- s6.app.utils.rim_solve.fit_ellipse_conic(pts: ndarray) ndarray
Direct least-squares fit (Fitzgibbon-style) to conic: Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0 -> symmetric 3x3 matrix C.
- s6.app.utils.rim_solve.ellipse_params_from_conic(C: ndarray) tuple[float, float, float, float, float]
Convert 3x3 conic to (x0, y0, a_axis, b_axis, angle), with a >= b and angle the ellipse rotation (radians).
- s6.app.utils.rim_solve.conic_algebraic_residuals(C: ndarray, pts2: ndarray) ndarray
Algebraic residual r = x^T C x for homogeneous x = [u, v, 1]^T.
- s6.app.utils.rim_solve.loss_from_pose(C_obs: ndarray, K: ndarray, base_Z_mm: float, stick_l_mm: float, radius_mm: float, phi_deg: float, theta_deg: float, num_samples: int = 16) float
- s6.app.utils.rim_solve.refine_pose_gauss_newton(C_obs: ndarray, K: ndarray, base_Z_mm: float, stick_l_mm: float, radius_mm: float, phi_deg_init: float, theta_deg_init: float, iters: int = 4, eps: float = 1e-06) tuple[float, float]
Super tiny Gauss–Newton on (phi, theta) using finite differences on the algebraic conic residual loss. Few iterations are enough from our init.
- s6.app.utils.rim_solve.self_test()
- s6.app.utils.rim_solve.main()