1
0
forked from IQ.Lvbs/IQ.Pilot
Files
IQ.Pilot/selfdrive/ui/tests/test_ui/nav_demo_capture.py
2026-08-22 23:42:42 -05:00

187 lines
7.3 KiB
Python

#!/usr/bin/env python3
import argparse
import os
import pathlib
import subprocess
import shutil
import sys
import time
import importlib
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.test.helpers import with_processes
TEST_DIR = pathlib.Path(__file__).parent
OUTPUT_DIR = TEST_DIR / "nav_demo_report"
UI_DELAY = float(os.getenv("IQPILOT_NAV_DEMO_UI_DELAY", "0.75"))
VERSION = "0.10.1 / nav-ui-demo / 7864838 / Mar 09"
SCENE_SETTLE_S = float(os.getenv("IQPILOT_NAV_DEMO_SCENE_SETTLE_S", "0.04"))
SEED_REPEATS = int(os.getenv("IQPILOT_NAV_DEMO_SEED_REPEATS", "2"))
SEED_DELAY_S = float(os.getenv("IQPILOT_NAV_DEMO_SEED_DELAY_S", "0.02"))
SEED_REFRESH_EVERY = int(os.getenv("IQPILOT_NAV_DEMO_SEED_REFRESH_EVERY", "24"))
NAV_REPEATS = int(os.getenv("IQPILOT_NAV_DEMO_NAV_REPEATS", "2"))
NAV_DELAY_S = float(os.getenv("IQPILOT_NAV_DEMO_NAV_DELAY_S", "0.02"))
SEED_PUBLISH_DURATION_S = SEED_REPEATS * SEED_DELAY_S
NAV_SCENE_DURATION_S = NAV_REPEATS * NAV_DELAY_S
NAV_SCENES = ()
NAV_TIMELINE = ()
build_ui_pubmaster = None
publish_nav_scene = None
publish_onroad_seed = None
seed_ui_test_params = None
class NavDemoCapture:
def __init__(self, output_dir: pathlib.Path):
os.environ["SCALE"] = os.getenv("SCALE", "1")
os.environ["BIG"] = "1"
os.environ["RECORD"] = "1"
os.environ["RECORD_OUTPUT"] = str(output_dir / "nav_demo")
sys.modules["mouseinfo"] = False
self.output_dir = output_dir
self.pm = build_ui_pubmaster()
self.frames = []
self._image_lib = None
self.video_path = self.output_dir / "nav_demo.mp4"
def _load_image_lib(self):
if self._image_lib is not None:
return self._image_lib
try:
self._image_lib = importlib.import_module("PIL.Image")
except ModuleNotFoundError:
self._image_lib = None
return self._image_lib
def setup(self):
publish_onroad_seed(self.pm)
time.sleep(UI_DELAY)
@with_processes(["ui"])
def run(self):
self.setup()
for idx, scene in enumerate(NAV_TIMELINE):
if idx % SEED_REFRESH_EVERY == 0:
publish_onroad_seed(self.pm, repeats=SEED_REPEATS, delay=SEED_DELAY_S)
publish_nav_scene(self.pm, scene, repeats=NAV_REPEATS, delay=NAV_DELAY_S)
time.sleep(SCENE_SETTLE_S)
def extract_video_stills(self) -> list[pathlib.Path]:
if not self.video_path.exists():
return []
extracted = []
current_ts = UI_DELAY
for idx, scene in enumerate(NAV_TIMELINE):
if idx % SEED_REFRESH_EVERY == 0:
current_ts += SEED_PUBLISH_DURATION_S
capture_ts = current_ts + (NAV_SCENE_DURATION_S * 0.6)
capture_name = scene.get("capture_name")
if capture_name:
output_path = self.output_dir / f"{capture_name}.png"
subprocess.run(
[
"ffmpeg",
"-y",
"-loglevel", "error",
"-ss", f"{capture_ts:.2f}",
"-i", str(self.video_path),
"-frames:v", "1",
str(output_path),
],
check=True,
)
extracted.append(output_path)
current_ts += NAV_SCENE_DURATION_S + SCENE_SETTLE_S
return extracted
def write_gif(self) -> pathlib.Path | None:
if not self.frames:
for scene in NAV_SCENES:
image_path = self.output_dir / f"{scene['name']}.png"
if image_path.exists():
self.frames.append(self._load_image_lib().open(image_path).copy())
if not self.frames:
return None
if self._load_image_lib() is None:
return None
gif_path = self.output_dir / "nav_demo.gif"
self.frames[0].save(
gif_path,
save_all=True,
append_images=self.frames[1:],
duration=900,
loop=0,
)
return gif_path
def main():
parser = argparse.ArgumentParser(description="Run BIG raylib UI with a hard-coded nav route and capture screenshots/GIF.")
parser.add_argument("--output-dir", type=pathlib.Path, default=OUTPUT_DIR, help="Directory for screenshots and gif.")
parser.add_argument("--no-gif", action="store_true", help="Skip animated GIF creation.")
parser.add_argument("--mapbox-token", default="", help="Mapbox token for the demo prefix. Falls back to MAPBOX_TOKEN if omitted.")
parser.add_argument("--no-mapbox", action="store_true", help="Disable Mapbox for the demo and use only cached/offline tiles.")
parser.add_argument("--force-local-offline", action="store_true", help="Bypass both live and cached Mapbox so only the local offline provider can render.")
parser.add_argument("--offline-mbtiles", type=pathlib.Path, default=None, help="Path to a local raster MBTiles file for offline rendering.")
parser.add_argument("--offline-tile-root", type=pathlib.Path, default=None, help="Path to a local XYZ raster tile directory for offline rendering.")
parser.add_argument("--fixture", type=pathlib.Path, default=None, help="Route fixture JSON to drive the nav demo.")
parser.add_argument("--osmaps-mode", choices=("online", "offline", "both"), default=None,
help="Set the OnlineOSMaps/OfflineOSMaps params (the production source selection) for the demo prefix.")
args = parser.parse_args()
if args.output_dir.exists():
shutil.rmtree(args.output_dir)
args.output_dir.mkdir(parents=True)
mapbox_token = args.mapbox_token
if not mapbox_token and not args.no_mapbox:
existing = Params().get("MapboxToken")
if isinstance(existing, bytes):
existing = existing.decode("utf-8")
mapbox_token = existing or ""
with OpenpilotPrefix():
if args.fixture is not None:
os.environ["IQPILOT_NAV_DEMO_FIXTURE"] = str(args.fixture)
if args.force_local_offline:
os.environ["IQPILOT_DISABLE_MAPBOX_PROVIDER"] = "1"
os.environ["IQPILOT_DISABLE_MAPBOX_CACHE"] = "1"
elif args.no_mapbox:
os.environ.pop("IQPILOT_DISABLE_MAPBOX_PROVIDER", None)
os.environ.pop("IQPILOT_DISABLE_MAPBOX_CACHE", None)
if args.offline_mbtiles is not None:
os.environ["IQPILOT_OFFLINE_MBTILES"] = str(args.offline_mbtiles)
if args.offline_tile_root is not None:
os.environ["IQPILOT_OFFLINE_TILE_ROOT"] = str(args.offline_tile_root)
global NAV_SCENES, NAV_TIMELINE, build_ui_pubmaster, publish_nav_scene, publish_onroad_seed, seed_ui_test_params
nav_demo_common = importlib.import_module("openpilot.selfdrive.ui.tests.test_ui.nav_demo_common")
NAV_SCENES = nav_demo_common.NAV_SCENES
NAV_TIMELINE = nav_demo_common.NAV_TIMELINE
build_ui_pubmaster = nav_demo_common.build_ui_pubmaster
publish_nav_scene = nav_demo_common.publish_nav_scene
publish_onroad_seed = nav_demo_common.publish_onroad_seed
seed_ui_test_params = nav_demo_common.seed_ui_test_params
seed_ui_test_params(Params(), VERSION, mapbox_token=mapbox_token)
if args.osmaps_mode is not None:
demo_params = Params()
demo_params.put_bool("OnlineOSMaps", args.osmaps_mode in ("online", "both"))
demo_params.put_bool("OfflineOSMaps", args.osmaps_mode in ("offline", "both"))
demo = NavDemoCapture(args.output_dir)
demo.run()
demo.extract_video_stills()
gif_path = None if args.no_gif else demo.write_gif()
print(f"Screenshots written to: {args.output_dir}")
if demo.video_path.exists():
print(f"Recorded preview written to: {demo.video_path}")
if gif_path is not None:
print(f"Animated preview written to: {gif_path}")
if __name__ == "__main__":
main()