mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-22 22:06:37 +00:00
fix: post-audit — runtime bugs, functional gaps, docs, hardening
Phase A — Runtime bugs: - SSE: add push_event() method to SSEEventStreamer (was missing, masked by mocks) - MAVLink: satellites_visible=10 (was 0, triggers ArduPilot failsafe) - MAVLink: horiz_accuracy=sqrt(P[0,0]+P[1,1]) per spec (was sqrt(avg)) - MAVLink: MEDIUM confidence → fix_type=3 per solution.md (was 2) Phase B — Functional gaps: - handle_user_fix() injects operator GPS into ESKF with noise=500m - app.py uses create_vo_backend() factory (was hardcoded SequentialVO) - ESKF: Mahalanobis gating on satellite updates (rejects outliers >5σ) - ESKF: public accessors (position, quaternion, covariance, last_timestamp) - Processor: no more private ESKF field access Phase C — Documentation: - README: correct API endpoints, CLI command, 40+ env vars documented - Dockerfile: ENV prefixes match pydantic-settings (DB_, SATELLITE_, MAVLINK_) - tech_stack.md marked ARCHIVED (contradicts solution.md) Phase D — Hardening: - JWT auth middleware (AUTH_ENABLED=false default, verify_token on /flights) - TLS config env vars (AUTH_SSL_CERTFILE, AUTH_SSL_KEYFILE) - SHA-256 tile manifest verification in SatelliteDataManager - AuthConfig, ESKFSettings, MAVLinkConfig, SatelliteConfig in config.py Also: conftest.py shared fixtures, download_tiles.py, convert_to_trt.py scripts, config wiring into app.py lifespan, config-driven ESKF, calculate_precise_angle fix. Tests: 196 passed / 8 skipped. Ruff clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""TensorRT engine conversion script.
|
||||
|
||||
Converts ONNX models to TensorRT FP16 .engine files for Jetson deployment.
|
||||
Wraps trtexec CLI (available on Jetson with JetPack installed).
|
||||
|
||||
Usage:
|
||||
# Convert a single model
|
||||
python scripts/convert_to_trt.py --onnx weights/litesam.onnx --output /opt/engines/litesam.engine
|
||||
|
||||
# Convert all known models from weights/ to engines/
|
||||
python scripts/convert_to_trt.py --all --onnx-dir weights/ --engine-dir /opt/engines/
|
||||
|
||||
# Dry run (check trtexec availability)
|
||||
python scripts/convert_to_trt.py --check
|
||||
|
||||
Requires:
|
||||
- NVIDIA TensorRT (trtexec in PATH) — available on Jetson with JetPack
|
||||
- NOT available on dev/CI machines — script exits cleanly with a message
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Known models and their expected ONNX filenames
|
||||
_MODELS = {
|
||||
"superpoint": "superpoint.onnx",
|
||||
"lightglue": "lightglue.onnx",
|
||||
"xfeat": "xfeat.onnx",
|
||||
"dinov2": "dinov2.onnx",
|
||||
"litesam": "litesam.onnx",
|
||||
}
|
||||
|
||||
|
||||
def find_trtexec() -> str | None:
|
||||
"""Find trtexec binary in PATH or common Jetson locations."""
|
||||
path = shutil.which("trtexec")
|
||||
if path:
|
||||
return path
|
||||
# Common Jetson paths
|
||||
for candidate in [
|
||||
"/usr/src/tensorrt/bin/trtexec",
|
||||
"/usr/local/cuda/bin/trtexec",
|
||||
]:
|
||||
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def convert_onnx_to_engine(
|
||||
onnx_path: str,
|
||||
engine_path: str,
|
||||
fp16: bool = True,
|
||||
workspace_mb: int = 1024,
|
||||
trtexec_path: str | None = None,
|
||||
) -> bool:
|
||||
"""Run trtexec to convert ONNX → TensorRT engine.
|
||||
|
||||
Returns True on success, False on failure.
|
||||
"""
|
||||
trtexec = trtexec_path or find_trtexec()
|
||||
if not trtexec:
|
||||
print("ERROR: trtexec not found. Install TensorRT or run on Jetson with JetPack.")
|
||||
return False
|
||||
|
||||
if not os.path.isfile(onnx_path):
|
||||
print(f"ERROR: ONNX file not found: {onnx_path}")
|
||||
return False
|
||||
|
||||
os.makedirs(os.path.dirname(engine_path) or ".", exist_ok=True)
|
||||
|
||||
cmd = [
|
||||
trtexec,
|
||||
f"--onnx={onnx_path}",
|
||||
f"--saveEngine={engine_path}",
|
||||
f"--workspace={workspace_mb}",
|
||||
]
|
||||
if fp16:
|
||||
cmd.append("--fp16")
|
||||
|
||||
print(f" Converting: {onnx_path} → {engine_path}")
|
||||
print(f" Command: {' '.join(cmd)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
||||
if result.returncode == 0:
|
||||
size_mb = os.path.getsize(engine_path) / (1024 * 1024)
|
||||
print(f" OK: {engine_path} ({size_mb:.1f} MB)")
|
||||
return True
|
||||
else:
|
||||
print(f" FAIL (exit {result.returncode})")
|
||||
if result.stderr:
|
||||
print(f" stderr: {result.stderr[:500]}")
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
print(" FAIL: trtexec timed out (>600s)")
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print(f" FAIL: trtexec not found at {trtexec}")
|
||||
return False
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Convert ONNX models to TensorRT FP16 engines")
|
||||
parser.add_argument("--onnx", help="Input ONNX model path")
|
||||
parser.add_argument("--output", help="Output .engine path")
|
||||
parser.add_argument("--all", action="store_true", help="Convert all known models")
|
||||
parser.add_argument("--onnx-dir", default="weights", help="Directory with ONNX files")
|
||||
parser.add_argument("--engine-dir", default="/opt/engines", help="Output engine directory")
|
||||
parser.add_argument("--no-fp16", action="store_true", help="Disable FP16 (use FP32)")
|
||||
parser.add_argument("--workspace", type=int, default=1024, help="TRT workspace (MB)")
|
||||
parser.add_argument("--check", action="store_true", help="Check trtexec availability only")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check mode
|
||||
if args.check:
|
||||
trtexec = find_trtexec()
|
||||
if trtexec:
|
||||
print(f"trtexec found: {trtexec}")
|
||||
return 0
|
||||
else:
|
||||
print("trtexec not found. Not on Jetson or TensorRT not installed.")
|
||||
return 1
|
||||
|
||||
fp16 = not args.no_fp16
|
||||
|
||||
# Single model conversion
|
||||
if args.onnx and args.output:
|
||||
ok = convert_onnx_to_engine(args.onnx, args.output, fp16=fp16, workspace_mb=args.workspace)
|
||||
return 0 if ok else 1
|
||||
|
||||
# Batch conversion
|
||||
if args.all:
|
||||
trtexec = find_trtexec()
|
||||
if not trtexec:
|
||||
print("trtexec not found. Conversion requires Jetson with JetPack installed.")
|
||||
return 1
|
||||
|
||||
print(f"Converting all known models from {args.onnx_dir}/ → {args.engine_dir}/")
|
||||
success = 0
|
||||
fail = 0
|
||||
for model_name, onnx_file in _MODELS.items():
|
||||
onnx_path = os.path.join(args.onnx_dir, onnx_file)
|
||||
engine_path = os.path.join(args.engine_dir, f"{model_name}.engine")
|
||||
if not os.path.isfile(onnx_path):
|
||||
print(f" SKIP {model_name}: {onnx_path} not found")
|
||||
continue
|
||||
ok = convert_onnx_to_engine(
|
||||
onnx_path, engine_path,
|
||||
fp16=fp16, workspace_mb=args.workspace, trtexec_path=trtexec,
|
||||
)
|
||||
if ok:
|
||||
success += 1
|
||||
else:
|
||||
fail += 1
|
||||
|
||||
print(f"\nDone: {success} converted, {fail} failed")
|
||||
return 1 if fail > 0 else 0
|
||||
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user