haiyizxx commited on
Commit
bf0c1ca
Β·
1 Parent(s): ab2340a
Files changed (4) hide show
  1. Dockerfile +11 -0
  2. README.md +14 -6
  3. app.py +95 -0
  4. 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: Cvat Webhook
3
- emoji: πŸŒ–
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: docker
7
- pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
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]