"""Gradio-SDK entry point (free accounts / ZeroGPU hardware). ZeroGPU only accepts apps started through Gradio's launch(), so this starts a small Gradio page and serves the same FastAPI service (serve/main.py) under /v1. Inference runs on the CPU. Gradio's own middleware answers GET requests it doesn't recognise with its HTML page, so requests for /v1 are dispatched to FastAPI at the ASGI-server level, before Gradio sees them. """ import logging import os os.environ.setdefault("MODEL_PATH", "models/model.onnx") import gradio as gr # noqa: E402 from starlette.routing import Mount # noqa: E402 try: # ZeroGPU expects at least one @spaces.GPU function; it is never called. import spaces @spaces.GPU def _unused_gpu_placeholder(): return None except ImportError: pass from serve.main import create_app # noqa: E402 api = create_app(load_model=True) INFO = """ # PCB defect detection API FastAPI + ONNX Runtime (YOLO11n, static INT8, detection head FP32), CPU inference. On this Space the API is mounted under **/v1**: - `POST /v1/predict`: multipart field `file`; query `conf`, and `visualize=true` for an annotated PNG - `GET /v1/health`, `GET /v1/metrics` (Prometheus), `GET /v1/docs` (OpenAPI) ```bash curl -F "file=@board.jpg;type=image/jpeg" https://michaelandrew35-pcb-defect-api.hf.space/v1/predict ``` Code, tests, CI and benchmarks: https://gitlab.com/michael.sucahyo/pcb-defect-optimization """ with gr.Blocks(title="PCB defect detection API") as demo: gr.Markdown(INFO) PREFIX = "/v1" class PrefixDispatch: """Send /v1 requests to the FastAPI service and everything else to Gradio.""" def __init__(self, fallback, api): self.fallback, self.api = fallback, api async def __call__(self, scope, receive, send): path = scope.get("path", "") if scope["type"] in ("http", "websocket") and (path == PREFIX or path.startswith(PREFIX + "/")): scope = dict(scope, root_path=scope.get("root_path", "") + PREFIX) return await self.api(scope, receive, send) return await self.fallback(scope, receive, send) demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860")), prevent_thread_lock=True) demo.app.router.routes.insert(0, Mount(PREFIX, app=api)) # fallback for connections opened before the swap try: # uvicorn hands config.loaded_app to every new connection cfg = demo.server.config cfg.loaded_app = PrefixDispatch(cfg.loaded_app, api) logging.getLogger("pcb_serve").info("routing %s to FastAPI ahead of Gradio", PREFIX) except AttributeError as exc: logging.getLogger("pcb_serve").warning("could not install /v1 dispatcher: %s", exc) demo.block_thread()