#!/usr/bin/env python3
"""Live two-panel viewer — the product, run demo-time by the boss.
pixi run demo [--source scene|webcam]
WINDOW 1 "Sparrow — overlay": live scene with tracked features and short
motion tails drawn on top, plus a metric strip.
WINDOW 2 "Sparrow — sparse top-down map": the sparse map panel, updating
live from map.snapshot(): points + the camera trail.
"""
import argparse
import cv2
import numpy as np
import core
import scene
from overlay import draw_overlay
from topmap import draw_topmap
FPS = 30.0
def frame_source(kind):
if kind == "webcam":
cap = cv2.VideoCapture(0)
while True:
ok, f = cap.read()
if not ok:
break
yield f
return
t = 0.0
while True:
yield scene.render(t)
t += 1 / FPS
def metric_strip(info, side="bottom"):
h = 22
return f"fps≈{info.get('fps', 0):4.0f} features {info.get('features', 0):3d} lost {info.get('lost_rate', 0):4.2f} map {info.get('map', 0):4d}"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--source", default="scene", choices=["scene", "webcam"])
a = ap.parse_args()
pipe = core.Pipeline()
t_prev = cv2.getTickCount()
trail = []
for frame in frame_source(a.source):
res = pipe.on_frame(frame)
dt = (cv2.getTickCount() - t_prev) / cv2.getTickFrequency()
t_prev = cv2.getTickCount()
fps = 1.0 / max(dt, 1e-3)
snap = pipe.map.snapshot()
info = {"fps": fps, "features": res["features"], "lost": res["lost"],
"map": snap.get("count", 0)}
# camera trail for the top-down panel (image x ≈ scene x for the
# seed — real pose estimation is T3+, engineers, you know the way)
cam_xy = snap.get("camera_xy")
if cam_xy:
trail.append(tuple(cam_xy))
trail[:] = trail[-250:]
vis = draw_overlay(frame.copy(), snap, info)
top = draw_topmap(snap, trail, (640, 400))
strip_rect = (0, vis.shape[0] - 24, vis.shape[1], vis.shape[0])
cv2.rectangle(vis, strip_rect[:2], (strip_rect[2], strip_rect[3]), (18, 18, 18), -1)
cv2.putText(vis, metric_strip(info), (8, vis.shape[0] - 8),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (220, 255, 220), 1)
cv2.imshow("Sparrow — overlay", vis)
cv2.imshow("Sparrow — sparse top-down map", top)
if cv2.waitKey(1) == 27:
break
cv2.destroyAllWindows()
if __name__ == "__main__":
main()