MB-IDK commited on
Commit
4fa696a
Β·
verified Β·
1 Parent(s): 9c74f36

Upload 3 files

Browse files
Files changed (3) hide show
  1. app_py.py +96 -0
  2. dockerfile (4).txt +39 -0
  3. requirements.txt +11 -0
app_py.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import numpy as np
4
+ from PIL import Image
5
+ from ultralytics import YOLO
6
+ from huggingface_hub import hf_hub_download
7
+
8
+ # ── Model ──────────────────────────────────────────────────────────────────────
9
+ MODEL_REPO = "DannyLuna/recaptcha-classification-57k"
10
+ MODEL_FILE = "recaptcha_classification_57k.onnx"
11
+ MODEL_PATH = os.path.join("models", MODEL_FILE)
12
+
13
+ os.makedirs("models", exist_ok=True)
14
+
15
+ if not os.path.exists(MODEL_PATH):
16
+ print(f"[INFO] Downloading {MODEL_FILE} from {MODEL_REPO}...")
17
+ hf_hub_download(
18
+ repo_id=MODEL_REPO,
19
+ filename=MODEL_FILE,
20
+ local_dir="models",
21
+ )
22
+ print("[INFO] Download complete.")
23
+
24
+ model = YOLO(MODEL_PATH, task="classify")
25
+ print(f"[INFO] Model loaded. Classes: {list(model.names.values())}")
26
+
27
+ # Classes that the solver considers "real" matches (not background)
28
+ TARGET_CLASSES = {
29
+ "bicycle", "bridge", "bus", "car", "chimney", "crosswalk",
30
+ "fire hydrant", "motorcycle", "mountain", "palm tree",
31
+ "stairs", "tractor", "traffic light",
32
+ }
33
+
34
+ # ── Inference ──────────────────────────────────────────────────────────────────
35
+ def predict(image: Image.Image):
36
+ if image is None:
37
+ return {}, "⚠️ No image provided."
38
+
39
+ results = model(image, verbose=False)
40
+ probs = results[0].probs
41
+
42
+ # Build full confidence dict for the label widget
43
+ conf_dict = {
44
+ model.names[i]: float(probs.data[i])
45
+ for i in range(len(model.names))
46
+ }
47
+
48
+ top1_name = model.names[int(probs.top1)]
49
+ top1_conf = float(probs.top1conf)
50
+ is_target = top1_name in TARGET_CLASSES
51
+
52
+ label_emoji = "βœ…" if is_target else "🚫"
53
+ status = (
54
+ f"{label_emoji} **{top1_name.upper()}** β€” {top1_conf:.1%} confidence"
55
+ + ("" if is_target else "\n*(classified as `other` / background)*")
56
+ )
57
+
58
+ return conf_dict, status
59
+
60
+
61
+ # ── UI ─────────────────────────────────────────────────────────────────────────
62
+ CLASSES_MD = ", ".join(f"`{c}`" for c in sorted(TARGET_CLASSES))
63
+
64
+ with gr.Blocks(title="reCAPTCHA Classifier", theme=gr.themes.Soft()) as demo:
65
+ gr.Markdown(
66
+ f"""
67
+ # πŸ” reCAPTCHA Image Classifier
68
+ YOLO-based classification model fine-tuned on **57k reCAPTCHA tiles**.
69
+ Upload a tile image and the model will predict which class it belongs to.
70
+
71
+ **Target classes:** {CLASSES_MD} + `other`
72
+ """
73
+ )
74
+
75
+ with gr.Row():
76
+ with gr.Column(scale=1):
77
+ img_input = gr.Image(type="pil", label="Upload tile image")
78
+ run_btn = gr.Button("πŸš€ Classify", variant="primary")
79
+
80
+ with gr.Column(scale=1):
81
+ status_out = gr.Markdown(label="Top prediction")
82
+ label_out = gr.Label(
83
+ num_top_classes=5,
84
+ label="Top-5 class probabilities",
85
+ )
86
+
87
+ run_btn.click(fn=predict, inputs=img_input, outputs=[label_out, status_out])
88
+ img_input.change(fn=predict, inputs=img_input, outputs=[label_out, status_out])
89
+
90
+ gr.Examples(
91
+ examples=[], # drop your own example images here
92
+ inputs=img_input,
93
+ )
94
+
95
+ if __name__ == "__main__":
96
+ demo.launch(server_name="0.0.0.0", server_port=7860)
dockerfile (4).txt ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ── Base ───────────────────────────────────────────────────────────────────────
2
+ # slim image keeps the layer small; HF free tier is CPU-only
3
+ FROM python:3.11-slim
4
+
5
+ # ── System deps ────────────────────────────────────────────────────────────────
6
+ # libgl1 + libglib2.0-0 are required by OpenCV (pulled in by ultralytics)
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ libgl1 \
9
+ libglib2.0-0 \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # ── HF Spaces user (uid 1000) ──────────────────────────────────────────────────
13
+ # Spaces runs as a non-root user; pre-create it so chown works
14
+ RUN useradd -m -u 1000 user
15
+ USER user
16
+
17
+ ENV HOME=/home/user \
18
+ PATH="/home/user/.local/bin:$PATH" \
19
+ PYTHONUNBUFFERED=1 \
20
+ # tell HF hub to cache inside the writable home dir
21
+ HF_HOME=/home/user/.cache/huggingface \
22
+ # disable YOLO's attempt to write analytics / config to /root
23
+ YOLO_CONFIG_DIR=/home/user/.config/ultralytics
24
+
25
+ WORKDIR /home/user/app
26
+
27
+ # ── Python deps ────────────────────────────────────────────────────────────────
28
+ # Copy requirements first so Docker can cache this layer
29
+ COPY --chown=user requirements.txt .
30
+ RUN pip install --no-cache-dir --upgrade pip \
31
+ && pip install --no-cache-dir -r requirements.txt
32
+
33
+ # ── App code ───────────────────────────────────────────────────────────────────
34
+ COPY --chown=user . .
35
+
36
+ # ── Runtime ────────────────────────────────────────────────────────────────────
37
+ EXPOSE 7860
38
+
39
+ CMD ["python", "app.py"]
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # YOLO inference (CPU) β€” pulls in torch, torchvision, opencv-python-headless, etc.
2
+ ultralytics>=8.3.0
3
+
4
+ # ONNX runtime for CPU inference (lighter than the full pytorch weights)
5
+ onnxruntime>=1.18.0
6
+
7
+ # HF model download
8
+ huggingface_hub>=0.23.0
9
+
10
+ # UI
11
+ gradio>=4.44.0