f34817d5b6
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.
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
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",
|
|
)
|