Files
fhd_scan_web_app/backend/routers/camera.py
T
gardentech f34817d5b6 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.
2026-08-07 14:26:00 +09:00

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",
)