Initial commit: scan_web integrated recording UI
FastAPI backend + vanilla JS frontend for LiDAR/camera startup, camera settings, and rosbag recording, unifying the previous scan_gui/scan_gui_dual/ scan_gui_triple desktop tools into one web app.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
import camera_params
|
||||
import config
|
||||
|
||||
router = APIRouter(prefix="/api/camera", tags=["camera"])
|
||||
|
||||
|
||||
class ParamValuesIn(BaseModel):
|
||||
exposure_auto: bool
|
||||
exposure_auto_target_brightness: int
|
||||
exposure_auto_min: float
|
||||
exposure_auto_max: float
|
||||
exposure_time: int
|
||||
gain: float
|
||||
|
||||
|
||||
def _get_spec(request: Request, cam_id: str) -> config.CameraSpec:
|
||||
session = request.app.state.session
|
||||
specs = {c.id: c for c in config.camera_specs(session.camera_count)}
|
||||
spec = specs.get(cam_id)
|
||||
if spec is None:
|
||||
raise HTTPException(404, f"unknown camera id '{cam_id}' for camera_count={session.camera_count}")
|
||||
return spec
|
||||
|
||||
|
||||
@router.get("/{cam_id}/params")
|
||||
async def get_params(cam_id: str, request: Request):
|
||||
spec = _get_spec(request, cam_id)
|
||||
session = request.app.state.session
|
||||
values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec)
|
||||
session.camera_values[cam_id] = values
|
||||
return values.as_dict()
|
||||
|
||||
|
||||
@router.post("/{cam_id}/params")
|
||||
async def set_params(cam_id: str, body: ParamValuesIn, request: Request):
|
||||
spec = _get_spec(request, cam_id)
|
||||
session = request.app.state.session
|
||||
values = camera_params.CameraParamValues(**body.model_dump())
|
||||
session.camera_values[cam_id] = values
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/{cam_id}/params/apply")
|
||||
async def apply_params(cam_id: str, request: Request):
|
||||
spec = _get_spec(request, cam_id)
|
||||
session = request.app.state.session
|
||||
pm = request.app.state.pm
|
||||
if not pm.is_running("camera"):
|
||||
raise HTTPException(409, "camera is not running")
|
||||
values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec)
|
||||
results = await run_in_threadpool(camera_params.apply_params, spec, values)
|
||||
ok = all(r["ok"] for r in results)
|
||||
return {"ok": ok, "results": results}
|
||||
|
||||
|
||||
@router.post("/{cam_id}/params/save")
|
||||
async def save_params(cam_id: str, request: Request):
|
||||
spec = _get_spec(request, cam_id)
|
||||
session = request.app.state.session
|
||||
values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec)
|
||||
try:
|
||||
await run_in_threadpool(camera_params.save_params, spec, values)
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"save failed: {e}")
|
||||
return {"ok": True, "path": spec.base_params_path}
|
||||
|
||||
|
||||
@router.get("/{cam_id}/stream.mjpg")
|
||||
async def stream(cam_id: str, request: Request):
|
||||
_get_spec(request, cam_id) # validate
|
||||
ros = request.app.state.ros
|
||||
return StreamingResponse(
|
||||
ros.mjpeg_generator(cam_id),
|
||||
media_type="multipart/x-mixed-replace; boundary=frame",
|
||||
)
|
||||
Reference in New Issue
Block a user