2026-01-20 17:42:18 +08:00
|
|
|
import json
|
|
|
|
|
from typing import List, Optional
|
|
|
|
|
|
2026-01-21 13:49:01 +08:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Body
|
|
|
|
|
from pydantic import BaseModel
|
2026-01-20 17:42:18 +08:00
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
from db.crud import (
|
|
|
|
|
create_roi,
|
|
|
|
|
delete_roi,
|
|
|
|
|
get_all_rois,
|
|
|
|
|
get_roi_by_id,
|
|
|
|
|
get_roi_points,
|
|
|
|
|
update_roi,
|
|
|
|
|
)
|
|
|
|
|
from db.models import get_db
|
|
|
|
|
from inference.pipeline import get_pipeline
|
2026-01-20 17:46:32 +08:00
|
|
|
from inference.roi.cache_manager import get_roi_cache
|
2026-01-20 17:42:18 +08:00
|
|
|
from inference.roi.roi_filter import ROIFilter
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/camera", tags=["ROI管理"])
|
|
|
|
|
|
|
|
|
|
|
2026-01-21 13:49:01 +08:00
|
|
|
class CreateROIRequest(BaseModel):
|
|
|
|
|
roi_id: str
|
|
|
|
|
name: str
|
|
|
|
|
roi_type: str
|
|
|
|
|
points: List[List[float]]
|
|
|
|
|
rule_type: str
|
|
|
|
|
direction: Optional[str] = None
|
|
|
|
|
stay_time: Optional[int] = None
|
2026-01-22 16:44:26 +08:00
|
|
|
threshold_sec: int = 300
|
|
|
|
|
confirm_sec: int = 10
|
|
|
|
|
return_sec: int = 30
|
|
|
|
|
working_hours: Optional[List[dict]] = None
|
2026-01-21 13:49:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class UpdateROIRequest(BaseModel):
|
|
|
|
|
name: Optional[str] = None
|
|
|
|
|
roi_type: Optional[str] = None
|
|
|
|
|
points: Optional[List[List[float]]] = None
|
|
|
|
|
rule_type: Optional[str] = None
|
|
|
|
|
direction: Optional[str] = None
|
|
|
|
|
stay_time: Optional[int] = None
|
|
|
|
|
enabled: Optional[bool] = None
|
|
|
|
|
threshold_sec: Optional[int] = None
|
|
|
|
|
confirm_sec: Optional[int] = None
|
|
|
|
|
return_sec: Optional[int] = None
|
2026-01-22 16:44:26 +08:00
|
|
|
working_hours: Optional[List[dict]] = None
|
2026-01-21 13:49:01 +08:00
|
|
|
|
|
|
|
|
|
2026-01-20 17:46:32 +08:00
|
|
|
def _invalidate_roi_cache(camera_id: int):
|
|
|
|
|
pipeline = get_pipeline()
|
|
|
|
|
pipeline.roi_filter.clear_cache(camera_id)
|
|
|
|
|
get_roi_cache().invalidate(camera_id)
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 17:42:18 +08:00
|
|
|
@router.get("/{camera_id}/rois", response_model=List[dict])
|
|
|
|
|
def list_rois(camera_id: int, db: Session = Depends(get_db)):
|
|
|
|
|
roi_configs = get_all_rois(db, camera_id)
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"id": roi.id,
|
|
|
|
|
"roi_id": roi.roi_id,
|
|
|
|
|
"name": roi.name,
|
|
|
|
|
"type": roi.roi_type,
|
|
|
|
|
"points": json.loads(roi.points),
|
|
|
|
|
"rule": roi.rule_type,
|
|
|
|
|
"direction": roi.direction,
|
|
|
|
|
"stay_time": roi.stay_time,
|
|
|
|
|
"enabled": roi.enabled,
|
|
|
|
|
"threshold_sec": roi.threshold_sec,
|
|
|
|
|
"confirm_sec": roi.confirm_sec,
|
|
|
|
|
"return_sec": roi.return_sec,
|
2026-01-22 16:44:26 +08:00
|
|
|
"working_hours": json.loads(roi.working_hours) if roi.working_hours else None,
|
2026-01-20 17:42:18 +08:00
|
|
|
}
|
|
|
|
|
for roi in roi_configs
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{camera_id}/roi/{roi_id}", response_model=dict)
|
|
|
|
|
def get_roi(camera_id: int, roi_id: int, db: Session = Depends(get_db)):
|
|
|
|
|
roi = get_roi_by_id(db, roi_id)
|
|
|
|
|
if not roi:
|
|
|
|
|
raise HTTPException(status_code=404, detail="ROI不存在")
|
|
|
|
|
return {
|
|
|
|
|
"id": roi.id,
|
|
|
|
|
"roi_id": roi.roi_id,
|
|
|
|
|
"name": roi.name,
|
|
|
|
|
"type": roi.roi_type,
|
|
|
|
|
"points": json.loads(roi.points),
|
|
|
|
|
"rule": roi.rule_type,
|
|
|
|
|
"direction": roi.direction,
|
|
|
|
|
"stay_time": roi.stay_time,
|
|
|
|
|
"enabled": roi.enabled,
|
|
|
|
|
"threshold_sec": roi.threshold_sec,
|
|
|
|
|
"confirm_sec": roi.confirm_sec,
|
|
|
|
|
"return_sec": roi.return_sec,
|
2026-01-22 16:44:26 +08:00
|
|
|
"working_hours": json.loads(roi.working_hours) if roi.working_hours else None,
|
2026-01-20 17:42:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{camera_id}/roi", response_model=dict)
|
|
|
|
|
def add_roi(
|
|
|
|
|
camera_id: int,
|
2026-01-21 13:49:01 +08:00
|
|
|
request: CreateROIRequest,
|
2026-01-20 17:42:18 +08:00
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
2026-01-22 16:44:26 +08:00
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
working_hours_json = json.dumps(request.working_hours) if request.working_hours else None
|
|
|
|
|
|
2026-01-20 17:42:18 +08:00
|
|
|
roi = create_roi(
|
|
|
|
|
db,
|
|
|
|
|
camera_id=camera_id,
|
2026-01-21 13:49:01 +08:00
|
|
|
roi_id=request.roi_id,
|
|
|
|
|
name=request.name,
|
|
|
|
|
roi_type=request.roi_type,
|
|
|
|
|
points=request.points,
|
|
|
|
|
rule_type=request.rule_type,
|
|
|
|
|
direction=request.direction,
|
|
|
|
|
stay_time=request.stay_time,
|
|
|
|
|
threshold_sec=request.threshold_sec,
|
|
|
|
|
confirm_sec=request.confirm_sec,
|
|
|
|
|
return_sec=request.return_sec,
|
2026-01-22 16:44:26 +08:00
|
|
|
working_hours=working_hours_json,
|
2026-01-20 17:42:18 +08:00
|
|
|
)
|
|
|
|
|
|
2026-01-20 17:46:32 +08:00
|
|
|
_invalidate_roi_cache(camera_id)
|
2026-01-20 17:42:18 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"id": roi.id,
|
|
|
|
|
"roi_id": roi.roi_id,
|
|
|
|
|
"name": roi.name,
|
|
|
|
|
"type": roi.roi_type,
|
2026-01-21 13:49:01 +08:00
|
|
|
"points": request.points,
|
2026-01-20 17:42:18 +08:00
|
|
|
"rule": roi.rule_type,
|
2026-01-22 16:44:26 +08:00
|
|
|
"direction": roi.direction,
|
|
|
|
|
"stay_time": roi.stay_time,
|
2026-01-20 17:42:18 +08:00
|
|
|
"enabled": roi.enabled,
|
2026-01-22 16:44:26 +08:00
|
|
|
"threshold_sec": roi.threshold_sec,
|
|
|
|
|
"confirm_sec": roi.confirm_sec,
|
|
|
|
|
"return_sec": roi.return_sec,
|
|
|
|
|
"working_hours": request.working_hours,
|
2026-01-20 17:42:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/{camera_id}/roi/{roi_id}", response_model=dict)
|
|
|
|
|
def modify_roi(
|
|
|
|
|
camera_id: int,
|
|
|
|
|
roi_id: int,
|
2026-01-21 13:49:01 +08:00
|
|
|
request: UpdateROIRequest,
|
2026-01-20 17:42:18 +08:00
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
2026-01-22 16:44:26 +08:00
|
|
|
import json
|
|
|
|
|
working_hours_json = json.dumps(request.working_hours) if request.working_hours else None
|
|
|
|
|
|
2026-01-20 17:42:18 +08:00
|
|
|
roi = update_roi(
|
|
|
|
|
db,
|
|
|
|
|
roi_id=roi_id,
|
2026-01-21 13:49:01 +08:00
|
|
|
name=request.name,
|
|
|
|
|
points=request.points,
|
|
|
|
|
rule_type=request.rule_type,
|
|
|
|
|
direction=request.direction,
|
|
|
|
|
stay_time=request.stay_time,
|
|
|
|
|
enabled=request.enabled,
|
|
|
|
|
threshold_sec=request.threshold_sec,
|
|
|
|
|
confirm_sec=request.confirm_sec,
|
|
|
|
|
return_sec=request.return_sec,
|
2026-01-22 16:44:26 +08:00
|
|
|
working_hours=working_hours_json,
|
2026-01-20 17:42:18 +08:00
|
|
|
)
|
|
|
|
|
if not roi:
|
|
|
|
|
raise HTTPException(status_code=404, detail="ROI不存在")
|
|
|
|
|
|
2026-01-20 17:46:32 +08:00
|
|
|
_invalidate_roi_cache(camera_id)
|
2026-01-20 17:42:18 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"id": roi.id,
|
|
|
|
|
"roi_id": roi.roi_id,
|
|
|
|
|
"name": roi.name,
|
|
|
|
|
"type": roi.roi_type,
|
|
|
|
|
"points": json.loads(roi.points),
|
|
|
|
|
"rule": roi.rule_type,
|
|
|
|
|
"enabled": roi.enabled,
|
2026-01-22 16:44:26 +08:00
|
|
|
"working_hours": json.loads(roi.working_hours) if roi.working_hours else None,
|
2026-01-20 17:42:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{camera_id}/roi/{roi_id}")
|
|
|
|
|
def remove_roi(camera_id: int, roi_id: int, db: Session = Depends(get_db)):
|
|
|
|
|
if not delete_roi(db, roi_id):
|
|
|
|
|
raise HTTPException(status_code=404, detail="ROI不存在")
|
|
|
|
|
|
2026-01-20 17:46:32 +08:00
|
|
|
_invalidate_roi_cache(camera_id)
|
2026-01-20 17:42:18 +08:00
|
|
|
|
|
|
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{camera_id}/roi/validate")
|
|
|
|
|
def validate_roi(
|
|
|
|
|
camera_id: int,
|
|
|
|
|
roi_type: str,
|
|
|
|
|
points: List[List[float]],
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
try:
|
|
|
|
|
if roi_type == "polygon":
|
|
|
|
|
from shapely.geometry import Polygon
|
|
|
|
|
poly = Polygon(points)
|
|
|
|
|
is_valid = poly.is_valid
|
|
|
|
|
area = poly.area
|
|
|
|
|
elif roi_type == "line":
|
|
|
|
|
from shapely.geometry import LineString
|
|
|
|
|
line = LineString(points)
|
|
|
|
|
is_valid = True
|
|
|
|
|
area = 0.0
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(status_code=400, detail="不支持的ROI类型")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"valid": is_valid,
|
|
|
|
|
"area": area,
|
|
|
|
|
"message": "有效" if is_valid else "无效:自交或自重叠",
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|