update the cv modal inference proxy server with optimization
This commit is contained in:
@@ -18,6 +18,10 @@ from backend.implementation.triton_batch import TRITON_MAX_BATCH_SIZE
|
||||
from backend.services import cv_result_cache
|
||||
from backend.services import triton_runtime_service as triton_runtime
|
||||
from backend.services.cv_inference_service import CvBatchResult, CvInferenceOptions, run_batch, run_single
|
||||
from backend.services import cv_celery_service
|
||||
from backend.logging.logging_config import setup_logging
|
||||
|
||||
setup_logging()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -135,14 +139,15 @@ async def cv_inference_health():
|
||||
)
|
||||
|
||||
|
||||
@router.post("/analyze")
|
||||
@router.post("/analyze") # deprecated
|
||||
async def analyze_upload(
|
||||
image: UploadFile = File(...),
|
||||
calibration: str | None = Form(default=None),
|
||||
):
|
||||
"""Spec-compliant CV pipeline: CLAHE → angle → inflammation → conditional segmentation."""
|
||||
image_pil = await _load_upload_image(image)
|
||||
options = _build_options(_parse_calibration_form(calibration), use_cache=False)
|
||||
|
||||
options = _build_options(_parse_calibration_form(calibration), use_cache=True)
|
||||
|
||||
try:
|
||||
result = await run_single(image_pil, frame_id=None, options=options)
|
||||
@@ -168,11 +173,13 @@ async def analyze_batch_upload(
|
||||
calibration: str | None = Form(default=None),
|
||||
):
|
||||
"""Spec-compliant CV batch — one full pipeline per frame (angle-first, gated segmentation)."""
|
||||
logger.info("Starting analyze batch upload")
|
||||
if not images:
|
||||
raise HTTPException(status_code=400, detail="At least one image is required")
|
||||
|
||||
try:
|
||||
id_list = json.loads(frame_ids)
|
||||
# logger.info("Start to check the id_list")
|
||||
except json.JSONDecodeError as exc:
|
||||
raise HTTPException(status_code=400, detail="frame_ids must be a JSON array of strings") from exc
|
||||
|
||||
@@ -213,6 +220,68 @@ async def analyze_batch_upload(
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/analyze/batch/celery")
|
||||
async def analyze_batch_celery(
|
||||
images: list[UploadFile] = File(...),
|
||||
frame_ids: str = Form(...),
|
||||
calibration: str | None = Form(default=None),
|
||||
):
|
||||
"""Experiment: async chunk fan-out via Celery + Redis."""
|
||||
if not images:
|
||||
raise HTTPException(status_code=400, detail="At least one image is required")
|
||||
|
||||
try:
|
||||
id_list = json.loads(frame_ids)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise HTTPException(status_code=400, detail="frame_ids must be a JSON array of strings") from exc
|
||||
|
||||
if not isinstance(id_list, list) or not all(isinstance(x, str) for x in id_list):
|
||||
raise HTTPException(status_code=400, detail="frame_ids must be a JSON array of strings")
|
||||
if len(id_list) != len(images):
|
||||
raise HTTPException(status_code=400, detail="frame_ids length must match images count")
|
||||
|
||||
image_pils: list[Image.Image] = []
|
||||
for upload in images:
|
||||
image_pils.append(await _load_upload_image(upload))
|
||||
|
||||
options = _build_options(_parse_calibration_form(calibration))
|
||||
|
||||
try:
|
||||
job_id = cv_celery_service.submit_celery_batch(image_pils, id_list, options)
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"job_id": job_id,
|
||||
"image_count": len(image_pils),
|
||||
"mode": "celery-chunk-fanout",
|
||||
"chunk_size": cv_celery_service.CELERY_CHUNK_SIZE,
|
||||
})
|
||||
except Exception as exc:
|
||||
logger.exception("Celery batch submit failed")
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/analyze/batch/celery/{job_id}")
|
||||
async def analyze_batch_celery_result(job_id: str):
|
||||
"""Poll result for a Celery chunk-fan-out batch job."""
|
||||
try:
|
||||
result = await cv_celery_service.get_celery_batch_result(job_id)
|
||||
status = result.get("status")
|
||||
if status == "pending":
|
||||
return JSONResponse(
|
||||
status_code=202,
|
||||
content=result,
|
||||
)
|
||||
if status == "unknown":
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content=result,
|
||||
)
|
||||
return JSONResponse(result)
|
||||
except Exception as exc:
|
||||
logger.exception("Celery batch result fetch failed")
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/segment")
|
||||
@router.post("/segment/batch")
|
||||
@router.post("/angle")
|
||||
|
||||
27
workspace/sprint_1_2/CODEBASE/backend/routers/run_cv_inference.sh
Executable file
27
workspace/sprint_1_2/CODEBASE/backend/routers/run_cv_inference.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Launch the standalone CV inference FastAPI server (Modal Triton).
|
||||
#
|
||||
# Usage:
|
||||
# ./backend/run_cv_inference.sh
|
||||
#
|
||||
# Works from any directory: it resolves the CODEBASE root relative to this
|
||||
# script, sets PYTHONPATH so `import backend...` resolves, then runs the
|
||||
# server as a module.
|
||||
#
|
||||
# Override defaults via env vars, e.g.:
|
||||
# CV_INFERENCE_PORT=8080 ./backend/run_cv_inference.sh
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# CODEBASE root = grandparent dir of this script's directory (script lives in backend/routers/).
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CODEBASE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
cd "${CODEBASE_ROOT}"
|
||||
|
||||
export PYTHONPATH="${CODEBASE_ROOT}:${PYTHONPATH:-}"
|
||||
|
||||
# exec python -m backend.cv_inference_server
|
||||
|
||||
exec uvicorn backend.cv_inference_server:app --host 0.0.0.0 --port ${CV_INFERENCE_PORT:-8001}
|
||||
Reference in New Issue
Block a user