Spaces:
Sleeping
Sleeping
init
Browse files- Dockerfile +11 -0
- README.md +14 -6
- app.py +95 -0
- requirements.txt +2 -0
Dockerfile
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
|
| 3 |
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
| 4 |
+
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 8 |
+
|
| 9 |
+
COPY app.py .
|
| 10 |
+
|
| 11 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
-
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: CVAT Review Webhook
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
---
|
| 9 |
|
| 10 |
+
Listens for CVAT job completion webhooks and triggers the finish_review pipeline to merge reviewed masks back to HuggingFace.
|
| 11 |
+
|
| 12 |
+
## Setup
|
| 13 |
+
|
| 14 |
+
1. Create this Space on HuggingFace with Docker SDK
|
| 15 |
+
2. Set secrets: `HF_TOKEN`, `CVAT_TOKEN`, `GITHUB_PAT`, `HF_DATASET`
|
| 16 |
+
3. In CVAT project β Actions β Setup Webhooks:
|
| 17 |
+
- URL: `https://<your-space>.hf.space/webhook`
|
| 18 |
+
- Events: `update:job`
|
app.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CVAT webhook listener β triggers finish_review when a job is marked completed."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import subprocess
|
| 5 |
+
import sys
|
| 6 |
+
import tempfile
|
| 7 |
+
import threading
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
from fastapi import FastAPI, Request, HTTPException
|
| 11 |
+
|
| 12 |
+
app = FastAPI()
|
| 13 |
+
|
| 14 |
+
DATASET = os.environ.get("HF_DATASET", "ndimensions/sementic-segmentation-test")
|
| 15 |
+
CVAT_TOKEN = os.environ.get("CVAT_TOKEN", "")
|
| 16 |
+
CVAT_URL = os.environ.get("CVAT_URL", "https://app.cvat.ai")
|
| 17 |
+
GITHUB_PAT = os.environ.get("GITHUB_PAT", "")
|
| 18 |
+
REPO_URL = os.environ.get("REPO_URL", "https://github.com/rena-labs-ai/semantic-segmentation.git")
|
| 19 |
+
REPO_REF = os.environ.get("REPO_REF", "main")
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _clone_repo(workdir: Path) -> Path:
|
| 23 |
+
repo_url = REPO_URL
|
| 24 |
+
if GITHUB_PAT and "github.com" in repo_url:
|
| 25 |
+
repo_url = repo_url.replace("https://", f"https://{GITHUB_PAT}@")
|
| 26 |
+
repo_dir = workdir / "repo"
|
| 27 |
+
subprocess.run(
|
| 28 |
+
["git", "clone", "--depth", "1", "-b", REPO_REF, repo_url, str(repo_dir)],
|
| 29 |
+
check=True,
|
| 30 |
+
)
|
| 31 |
+
subprocess.run(
|
| 32 |
+
[sys.executable, "-m", "pip", "install", "-q", "-r", str(repo_dir / "requirements.txt")],
|
| 33 |
+
check=True,
|
| 34 |
+
)
|
| 35 |
+
return repo_dir
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _run_finish_review(repo_dir: Path, task_id: int) -> str:
|
| 39 |
+
result = subprocess.run(
|
| 40 |
+
[
|
| 41 |
+
sys.executable, str(repo_dir / "scripts" / "finish_review.py"),
|
| 42 |
+
"--task-id", str(task_id),
|
| 43 |
+
"--dataset", DATASET,
|
| 44 |
+
"--experiment", f"cvat_review_{task_id}",
|
| 45 |
+
"--labelmap", str(repo_dir / "labelmap.txt"),
|
| 46 |
+
"--cvat-url", CVAT_URL,
|
| 47 |
+
"--cvat-token", CVAT_TOKEN,
|
| 48 |
+
],
|
| 49 |
+
capture_output=True,
|
| 50 |
+
text=True,
|
| 51 |
+
cwd=str(repo_dir),
|
| 52 |
+
)
|
| 53 |
+
return result.stdout + result.stderr
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
@app.post("/webhook")
|
| 57 |
+
async def cvat_webhook(request: Request):
|
| 58 |
+
body = await request.json()
|
| 59 |
+
|
| 60 |
+
event = body.get("event", "")
|
| 61 |
+
if event != "update:job":
|
| 62 |
+
return {"status": "ignored", "event": event}
|
| 63 |
+
|
| 64 |
+
job = body.get("job", {})
|
| 65 |
+
state = job.get("state", "")
|
| 66 |
+
before = body.get("before_update", {})
|
| 67 |
+
prev_state = before.get("state", "")
|
| 68 |
+
|
| 69 |
+
if state != "completed" or prev_state == "completed":
|
| 70 |
+
return {"status": "ignored", "reason": f"state={state}, prev={prev_state}"}
|
| 71 |
+
|
| 72 |
+
task_id = job.get("task_id")
|
| 73 |
+
if not task_id:
|
| 74 |
+
raise HTTPException(status_code=400, detail="No task_id in payload")
|
| 75 |
+
|
| 76 |
+
print(f"Job completed β task_id={task_id}, running finish_review in background...")
|
| 77 |
+
|
| 78 |
+
def _run_in_background(tid: int):
|
| 79 |
+
try:
|
| 80 |
+
with tempfile.TemporaryDirectory() as workdir:
|
| 81 |
+
repo_dir = _clone_repo(Path(workdir))
|
| 82 |
+
output = _run_finish_review(repo_dir, tid)
|
| 83 |
+
print(output)
|
| 84 |
+
print(f"finish_review completed for task {tid}")
|
| 85 |
+
except Exception as exc:
|
| 86 |
+
print(f"finish_review failed for task {tid}: {exc}")
|
| 87 |
+
|
| 88 |
+
threading.Thread(target=_run_in_background, args=(task_id,), daemon=True).start()
|
| 89 |
+
|
| 90 |
+
return {"status": "accepted", "task_id": task_id}
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
@app.get("/health")
|
| 94 |
+
async def health():
|
| 95 |
+
return {"status": "ok", "dataset": DATASET}
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn[standard]
|