scene_service.web¶
Tiny live web UI for scene — top-down 2D canvas of objects + robot, plus a side panel listing every tracked object. Single static HTML served from /, JSON state at /api/state polled by the page at 5 Hz.
Bound on a separate port from FastMCP (default 50107) so the LLM / pilot path and the human-debug path don’t share a uvicorn — they have different latency tolerance and stop semantics. Lives in the same asyncio loop as the rest of scene though, so reading _REGISTRY is cheap (no IPC).
What it does NOT show yet: 3D pose Z, surface bbox extents, mapping service’s occupancy grid (mapping isn’t deployed yet — once it is, the JSON will grow an occupancy: field that the canvas overlays).
Functions
|
Build the Starlette ASGI app the entrypoint mounts on its own uvicorn server. |
- scene_service.web.make_app(*, registry: ObjectRegistry, hub: Any = None, detector: Any = None, sg_store: Any = None, anno_store: Any = None, object_store: Any = None, map_meta: Any = None, map_binding: dict | None = None, ops_lock: Lock | None = None, semantic_hold: dict | None = None) Starlette[source]¶
Build the Starlette ASGI app the entrypoint mounts on its own uvicorn server.
- Routes:
GET / — combined split layout (2D map · 3D · cam) GET /2d — 2D top-down map (occupancy grid + objects) GET /3d — 3D scene (point clouds + bbox; three.js) GET /cam — camera stack (live RGB + depth) GET /user — end-user map page (rooms: draw / rename /
confirm-stale / delete; light object overlay)
GET /api/state — JSON for the 2D map GET /api/objects3d — JSON for the 3D viz (per-object pcd + bbox) GET /api/camera — JSON: latest RGB + depth frames /api/annotations[..] — user annotation CRUD (see below) /api/maps[..] — scene-owned map library façade over map capabilities
hub is the SubscribersHub — passed so the JSON state can include the latest OccupancyGrid for the 2D canvas underlay. detector is the ConceptGraphsDetector — passed so the 3D endpoint can serialize its persistent MapObjectList. If None, the 3D page just shows an empty world. anno_store is the AnnotationStore backing the annotation CRUD; when None (store init failed / disabled) those routes answer 503. object_store is the scene-object snapshot store. Save writes the live registry into a fresh partition token; Load restores the token the sidecar (map_meta) points at. Neither touches the store’s own binding. map_meta is the MapMetaStore sidecar pairing each saved map with its object partition; when None, Save/Load degrade to annotations-only. map_binding is a mutable {map_id, mode, generation, source} dict shown by the /user page header, updated by Save/Load actions and by the lifecycle watcher (service.py) on a runtime epoch bump. ops_lock serializes Save/Load/Delete with each other AND with the watcher’s epoch response (service.py shares the same lock): the handlers suspend at RPC awaits mid-critical-section, and an interleaved flush or second Save would mix sessions/epochs in one snapshot.
Annotation API contract (STABLE once shipped — any frontend builds on it; see system/scene/README.md):
GET /api/annotations → {ok, annotations: […]} POST /api/annotations body {kind, name, points, theta?}
→ {ok, annotation}
- PUT /api/annotations/{id} body: any of {name, points, theta,
stale:false} → {ok, annotation}
DELETE /api/annotations/{id} → {ok}
theta (heading, radians) is poi-only — a room carrying it is a 400. On PUT, theta null/absent means “keep”; a set heading cannot be cleared, only changed (deliberate until the poi UI exists). Errors: 400 invalid body/fields, 404 unknown id, 503 store unavailable; those carry {ok: false, detail}. A store write failure (disk full) deliberately escapes as a plain 500 — the edit was NOT saved and hiding that behind a tidy body would be worse. Coordinates are map-frame meters. Same trust domain as the rest of this LAN debug/UI server — no auth.