#!/usr/bin/env python3 """Run the conditional engineering RTK--IMU 6DoF lever-arm branch.""" from __future__ import annotations import argparse import json import math import sys from dataclasses import asdict from pathlib import Path import numpy as np from scipy.spatial.transform import Rotation ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from rtk_imu.rtk_imu_engineering import ( DEFAULT_MANUAL_L_I_M, DEFAULT_MANUAL_L_I_COVARIANCE_M2, solve_engineering_6dof, ) from rtk_imu.rtk_imu_multisource import load_unified_sessions def _jsonable(value): if isinstance(value, np.ndarray): return _jsonable(value.tolist()) if isinstance(value, np.generic): return _jsonable(value.item()) if isinstance(value, float): return value if math.isfinite(value) else None if hasattr(value, "__dataclass_fields__"): return {key: _jsonable(item) for key, item in asdict(value).items()} if isinstance(value, dict): return {str(key): _jsonable(item) for key, item in value.items()} if isinstance(value, (list, tuple)): return [_jsonable(item) for item in value] return value def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--manifest", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) parser.add_argument( "--session", action="append", required=True, help="Dynamic/static sessions intentionally admitted to this engineering run.", ) parser.add_argument( "--rotation-rpy-deg", nargs=3, type=float, default=[0.4543066225, -0.0026392019, 0.0122384129], ) parser.add_argument( "--manual-l-i-m", nargs=3, type=float, default=DEFAULT_MANUAL_L_I_M.tolist(), help="Mechanical ANT1 phase-centre lever arm p_ANT1^I in metres.", ) prior_group = parser.add_mutually_exclusive_group() prior_group.add_argument( "--manual-l-i-std-m", nargs=3, type=float, default=np.sqrt(np.diag(DEFAULT_MANUAL_L_I_COVARIANCE_M2)).tolist(), help="Mechanical 1-sigma prior standard deviation for lx, ly, lz in metres.", ) prior_group.add_argument( "--manual-l-i-covariance-m2", nargs=9, type=float, help="Row-major 3x3 mechanical prior covariance in m^2.", ) parser.add_argument( "--no-manual-prior", action="store_true", help="Run a free solve only; retain the mechanical reference in no optimisation factor.", ) parser.add_argument( "--segment-id", action="append", help="Strict-continuity segment id from the excitation audit; may be repeated.", ) parser.add_argument("--sample-period-s", type=float, default=0.5) parser.add_argument("--skip-loo", action="store_true") parser.add_argument("--run-bootstrap", action="store_true") parser.add_argument("--bootstrap-repetitions", type=int, default=40) parser.add_argument("--bootstrap-seed", type=int, default=0) parser.add_argument("--run-rotation-sensitivity", action="store_true") args = parser.parse_args(argv) manual_l_i_m = None if args.no_manual_prior else args.manual_l_i_m prior_covariance = None if not args.no_manual_prior: if args.manual_l_i_std_m is not None: prior_covariance = np.diag(np.square(args.manual_l_i_std_m)) elif args.manual_l_i_covariance_m2 is not None: prior_covariance = np.asarray(args.manual_l_i_covariance_m2, dtype=float).reshape(3, 3) else: parser.error("mechanical prior requires --manual-l-i-std-m or --manual-l-i-covariance-m2") sessions = load_unified_sessions(args.manifest, selected_session_ids=set(args.session)) rotation = Rotation.from_euler("xyz", args.rotation_rpy_deg, degrees=True).as_matrix() result = solve_engineering_6dof( sessions, R_RTK_IMU=rotation, manual_l_I_m=manual_l_i_m, manual_l_I_covariance_m2=prior_covariance, sample_period_s=args.sample_period_s, run_loo=not args.skip_loo, run_bootstrap=args.run_bootstrap, bootstrap_repetitions=args.bootstrap_repetitions, bootstrap_seed=args.bootstrap_seed, run_rotation_sensitivity=args.run_rotation_sensitivity, selected_segment_ids=None if args.segment_id is None else set(args.segment_id), ) payload = { "data_only_6dof_accepted": False, "data_only_translation_accepted": result.data_only_translation_accepted, "engineering_6dof": _jsonable(result), } args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text( json.dumps(payload, ensure_ascii=False, indent=2, allow_nan=False) + "\n", encoding="utf-8" ) print(json.dumps(payload, ensure_ascii=False, indent=2, allow_nan=False)) return 0 if __name__ == "__main__": raise SystemExit(main())