doniramdani820 commited on
Commit
fbad51d
Β·
verified Β·
1 Parent(s): 3752132

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +25 -0
  2. app.py +366 -0
  3. best_model.onnx +3 -0
  4. data.yaml +12 -0
  5. deploy-to-hf.py +89 -0
  6. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Environment variables for ONNX Runtime 1.18.0
4
+ ENV PYTHONUNBUFFERED=1
5
+ ENV PORT=7860
6
+ ENV OMP_NUM_THREADS=1
7
+ ENV ORT_DISABLE_ALL_WARNINGS=1
8
+ ENV ONNXRUNTIME_DISABLE_STACK_EXECUTABILITY_WARNING=1
9
+
10
+ # Working directory
11
+ WORKDIR /app
12
+
13
+ # Copy and install Python requirements
14
+ COPY requirements.txt .
15
+ RUN pip install --no-cache-dir --upgrade pip && \
16
+ pip install --no-cache-dir -r requirements.txt
17
+
18
+ # Copy application files
19
+ COPY . .
20
+
21
+ # Expose port
22
+ EXPOSE 7860
23
+
24
+ # Start application with more verbose logging and timeout settings
25
+ CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--timeout-keep-alive", "300", "--log-level", "info"]
app.py ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import json
4
+ import base64
5
+ import secrets
6
+ from PIL import Image
7
+ import numpy as np
8
+
9
+ # Set environment variables for ONNX Runtime 1.18.0
10
+ os.environ['OMP_NUM_THREADS'] = '1'
11
+ os.environ['ORT_DISABLE_ALL_WARNINGS'] = '1'
12
+ os.environ['ONNXRUNTIME_DISABLE_STACK_EXECUTABILITY_WARNING'] = '1'
13
+
14
+ # Import ONNX Runtime 1.18.0 with error handling
15
+ try:
16
+ import onnxruntime as ort
17
+ print("βœ… ONNX Runtime 1.18.0 imported successfully")
18
+ except ImportError as e:
19
+ print(f"⚠️ ONNX Runtime import error: {e}")
20
+ print("πŸ”§ Applying workarounds for ONNX Runtime 1.18.0...")
21
+
22
+ # Workarounds for newer ONNX Runtime versions
23
+ import sys
24
+ import warnings
25
+ warnings.filterwarnings('ignore')
26
+
27
+ try:
28
+ import ctypes
29
+ # Disable executable stack warnings
30
+ libc = ctypes.CDLL("libc.so.6")
31
+ libc.personality(0x040000)
32
+ except:
33
+ pass
34
+
35
+ import onnxruntime as ort
36
+ print("βœ… ONNX Runtime 1.18.0 imported with workarounds")
37
+
38
+ from fastapi import FastAPI, HTTPException, Depends, Header
39
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
40
+ from pydantic import BaseModel
41
+ from typing import Optional, List
42
+ import yaml
43
+
44
+ # Load configuration
45
+ with open('data.yaml', 'r') as f:
46
+ config = yaml.safe_load(f)
47
+
48
+ # Initialize FastAPI app
49
+ app = FastAPI(
50
+ title="Geetest Slider Detection API",
51
+ description="ONNX-based slider position detection for Geetest captcha",
52
+ version="1.0.0"
53
+ )
54
+
55
+ # Security
56
+ security = HTTPBearer()
57
+ SECRET_KEY = os.getenv("API_SECRET_KEY", "DASDAS2")
58
+
59
+ # Load ONNX model with better error handling
60
+ session = None
61
+ model_loaded = False
62
+
63
+ def load_onnx_model():
64
+ global session, model_loaded
65
+ try:
66
+ # Check if model file exists
67
+ if not os.path.exists("best_model.onnx"):
68
+ print("⚠️ Model file 'best_model.onnx' not found")
69
+ return False
70
+
71
+ # ONNX Runtime 1.18.0 session creation with optimized settings
72
+ session_options = ort.SessionOptions()
73
+ session_options.enable_cpu_mem_arena = False
74
+ session_options.enable_mem_pattern = False
75
+ session_options.enable_mem_reuse = False
76
+ session_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
77
+ session_options.inter_op_num_threads = 1
78
+ session_options.intra_op_num_threads = 1
79
+
80
+ # CPU provider options for ONNX Runtime 1.18.0
81
+ cpu_provider_options = {
82
+ 'arena_extend_strategy': 'kSameAsRequested',
83
+ 'enable_cpu_mem_arena': '0'
84
+ }
85
+
86
+ providers = [('CPUExecutionProvider', cpu_provider_options)]
87
+
88
+ session = ort.InferenceSession(
89
+ "best_model.onnx",
90
+ providers=providers,
91
+ sess_options=session_options
92
+ )
93
+
94
+ print("βœ… ONNX model loaded successfully")
95
+ print(f"βœ… ONNX Runtime version: {ort.__version__}")
96
+ print(f"βœ… Using providers: {session.get_providers()}")
97
+ model_loaded = True
98
+ return True
99
+
100
+ except Exception as e:
101
+ print(f"❌ Error loading ONNX model: {e}")
102
+
103
+ # Fallback with basic configuration
104
+ try:
105
+ print("πŸ”§ Trying fallback configuration...")
106
+ session = ort.InferenceSession("best_model.onnx", providers=['CPUExecutionProvider'])
107
+ print("βœ… ONNX model loaded with fallback configuration")
108
+ model_loaded = True
109
+ return True
110
+ except Exception as fallback_error:
111
+ print(f"❌ Fallback failed: {fallback_error}")
112
+ print("πŸ’‘ Tip: Make sure 'best_model.onnx' is uploaded to the Space")
113
+ session = None
114
+ model_loaded = False
115
+ return False
116
+
117
+ # Try to load model on startup
118
+ load_onnx_model()
119
+
120
+ class PredictionRequest(BaseModel):
121
+ image: str # Base64 encoded image
122
+ task: str = "slider_detection"
123
+ confidence_threshold: float = 0.5
124
+
125
+ class BoundingBox(BaseModel):
126
+ x: float
127
+ y: float
128
+ width: float
129
+ height: float
130
+ confidence: float
131
+
132
+ class PredictionResponse(BaseModel):
133
+ success: bool
134
+ bbox: Optional[BoundingBox] = None
135
+ slider_position: Optional[float] = None
136
+ confidence: float = 0.0
137
+ message: str = ""
138
+
139
+ def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
140
+ """Verify API key for authentication"""
141
+ if credentials.credentials != SECRET_KEY:
142
+ raise HTTPException(
143
+ status_code=401,
144
+ detail="Invalid API key"
145
+ )
146
+ return credentials.credentials
147
+
148
+ def preprocess_image(image: Image.Image, target_size=(640, 640)):
149
+ """Preprocess image for ONNX model"""
150
+ try:
151
+ # Convert to RGB if needed
152
+ if image.mode != 'RGB':
153
+ image = image.convert('RGB')
154
+
155
+ # Resize while maintaining aspect ratio
156
+ original_size = image.size
157
+ image = image.resize(target_size, Image.LANCZOS)
158
+
159
+ # Convert to numpy array and normalize
160
+ img_array = np.array(image, dtype=np.float32)
161
+ img_array = img_array / 255.0 # Normalize to [0, 1]
162
+
163
+ # Transpose to CHW format (channels first)
164
+ img_array = np.transpose(img_array, (2, 0, 1))
165
+
166
+ # Add batch dimension
167
+ img_array = np.expand_dims(img_array, axis=0)
168
+
169
+ return img_array, original_size
170
+
171
+ except Exception as e:
172
+ raise HTTPException(status_code=400, detail=f"Error preprocessing image: {str(e)}")
173
+
174
+ def postprocess_predictions(outputs, original_size, target_size=(640, 640), confidence_threshold=0.5):
175
+ """Postprocess ONNX model outputs"""
176
+ try:
177
+ # Extract predictions (assuming YOLOv8 format)
178
+ predictions = outputs[0] # Shape: [1, 84, 8400] or similar
179
+
180
+ # Handle different output formats
181
+ if len(predictions.shape) == 3:
182
+ predictions = predictions[0] # Remove batch dimension
183
+
184
+ # Transpose if needed to get [num_boxes, features]
185
+ if predictions.shape[0] < predictions.shape[1]:
186
+ predictions = predictions.T
187
+
188
+ # Extract bbox coordinates and confidence
189
+ boxes = predictions[:, :4] # x, y, w, h
190
+ confidences = predictions[:, 4] # objectness score
191
+
192
+ # Filter by confidence
193
+ valid_indices = confidences > confidence_threshold
194
+
195
+ if not np.any(valid_indices):
196
+ return None
197
+
198
+ # Get best detection
199
+ best_idx = np.argmax(confidences)
200
+ best_box = boxes[best_idx]
201
+ best_conf = confidences[best_idx]
202
+
203
+ # Scale coordinates back to original image size
204
+ scale_x = original_size[0] / target_size[0]
205
+ scale_y = original_size[1] / target_size[1]
206
+
207
+ x_center, y_center, width, height = best_box
208
+
209
+ # Convert to absolute coordinates
210
+ x_center *= scale_x
211
+ y_center *= scale_y
212
+ width *= scale_x
213
+ height *= scale_y
214
+
215
+ # Convert center format to corner format
216
+ x = x_center - width / 2
217
+ y = y_center - height / 2
218
+
219
+ # Calculate slider position (x-coordinate of the gap/missing piece)
220
+ slider_position = x_center # Use center x as slider position
221
+
222
+ return BoundingBox(
223
+ x=float(x),
224
+ y=float(y),
225
+ width=float(width),
226
+ height=float(height),
227
+ confidence=float(best_conf)
228
+ ), float(slider_position)
229
+
230
+ except Exception as e:
231
+ print(f"Error in postprocessing: {e}")
232
+ return None, None
233
+
234
+ @app.get("/")
235
+ async def root():
236
+ """Health check endpoint"""
237
+ return {
238
+ "status": "ok",
239
+ "message": "Geetest Slider Detection API is running",
240
+ "model_loaded": model_loaded
241
+ }
242
+
243
+ @app.get("/")
244
+ async def root():
245
+ """Root endpoint - keeps Space alive and shows API info"""
246
+ return {
247
+ "message": "πŸš€ Geetest Slider API v1.0 - ONNX Runtime 1.18.0",
248
+ "status": "running",
249
+ "model_loaded": model_loaded,
250
+ "onnx_version": ort.__version__ if 'ort' in globals() else "not loaded",
251
+ "api_endpoints": {
252
+ "predict": "POST /predict (requires Authorization: Bearer token)",
253
+ "health": "GET /health",
254
+ "reload": "POST /reload-model (requires auth)"
255
+ },
256
+ "usage": "Send base64 image to /predict endpoint with your API key"
257
+ }
258
+
259
+ @app.get("/health")
260
+ async def health():
261
+ """Detailed health check"""
262
+ return {
263
+ "status": "healthy" if model_loaded else "unhealthy",
264
+ "model_loaded": model_loaded,
265
+ "onnx_providers": session.get_providers() if session else None,
266
+ "config": {
267
+ "classes": config.get('names', []),
268
+ "nc": config.get('nc', 0)
269
+ }
270
+ }
271
+
272
+ @app.post("/reload-model")
273
+ async def reload_model(api_key: str = Depends(verify_api_key)):
274
+ """Reload ONNX model (useful for debugging)"""
275
+ success = load_onnx_model()
276
+ return {
277
+ "success": success,
278
+ "model_loaded": model_loaded,
279
+ "message": "Model reloaded successfully" if success else "Failed to reload model"
280
+ }
281
+
282
+ @app.post("/predict", response_model=PredictionResponse)
283
+ async def predict_slider(
284
+ request: PredictionRequest,
285
+ api_key: str = Depends(verify_api_key)
286
+ ):
287
+ """Predict slider position from captcha image"""
288
+
289
+ if not model_loaded or session is None:
290
+ raise HTTPException(status_code=503, detail="Model not loaded - please upload best_model.onnx")
291
+
292
+ try:
293
+ # Decode base64 image
294
+ try:
295
+ image_data = base64.b64decode(request.image)
296
+ image = Image.open(io.BytesIO(image_data))
297
+ except Exception as e:
298
+ raise HTTPException(status_code=400, detail=f"Invalid image data: {str(e)}")
299
+
300
+ # Preprocess image
301
+ processed_image, original_size = preprocess_image(image)
302
+
303
+ # Run inference
304
+ input_name = session.get_inputs()[0].name
305
+ outputs = session.run(None, {input_name: processed_image})
306
+
307
+ # Postprocess results
308
+ bbox, slider_position = postprocess_predictions(
309
+ outputs,
310
+ original_size,
311
+ confidence_threshold=request.confidence_threshold
312
+ )
313
+
314
+ if bbox is None:
315
+ return PredictionResponse(
316
+ success=False,
317
+ message="No slider detection found",
318
+ confidence=0.0
319
+ )
320
+
321
+ return PredictionResponse(
322
+ success=True,
323
+ bbox=bbox,
324
+ slider_position=slider_position,
325
+ confidence=bbox.confidence,
326
+ message="Slider position detected successfully"
327
+ )
328
+
329
+ except HTTPException:
330
+ raise
331
+ except Exception as e:
332
+ raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
333
+
334
+ @app.post("/predict-batch")
335
+ async def predict_batch(
336
+ images: List[str],
337
+ confidence_threshold: float = 0.5,
338
+ api_key: str = Depends(verify_api_key)
339
+ ):
340
+ """Batch prediction for multiple images"""
341
+
342
+ if not model_loaded or session is None:
343
+ raise HTTPException(status_code=503, detail="Model not loaded - please upload best_model.onnx")
344
+
345
+ results = []
346
+ for i, img_base64 in enumerate(images):
347
+ try:
348
+ request = PredictionRequest(
349
+ image=img_base64,
350
+ confidence_threshold=confidence_threshold
351
+ )
352
+ result = await predict_slider(request, api_key)
353
+ results.append({"index": i, "result": result})
354
+ except Exception as e:
355
+ results.append({
356
+ "index": i,
357
+ "error": str(e),
358
+ "result": PredictionResponse(success=False, message=str(e))
359
+ })
360
+
361
+ return {"predictions": results}
362
+
363
+ if __name__ == "__main__":
364
+ import uvicorn
365
+ port = int(os.getenv("PORT", 7860))
366
+ uvicorn.run(app, host="0.0.0.0", port=port)
best_model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dd3b416a579604078e1b28849f292d90959ab7a4ce19d8c47b6cf0c5bf04901a
3
+ size 44731765
data.yaml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ names:
2
+ - slide_captcha - v4 slide captcha
3
+ nc: 1
4
+ roboflow:
5
+ license: CC BY 4.0
6
+ project: slider-vytcr
7
+ url: https://universe.roboflow.com/slider-hbeeu/slider-vytcr/dataset/2
8
+ version: 2
9
+ workspace: slider-hbeeu
10
+ test: ../test/images
11
+ train: ../train/images
12
+ val: ../valid/images
deploy-to-hf.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script untuk deploy ke Hugging Face Spaces
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ def run_command(cmd, cwd=None):
12
+ """Run shell command"""
13
+ try:
14
+ result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True)
15
+ if result.returncode != 0:
16
+ print(f"❌ Error running command: {cmd}")
17
+ print(f"Error: {result.stderr}")
18
+ return False
19
+ print(f"βœ… Success: {cmd}")
20
+ if result.stdout:
21
+ print(result.stdout)
22
+ return True
23
+ except Exception as e:
24
+ print(f"❌ Exception running command: {cmd}")
25
+ print(f"Error: {e}")
26
+ return False
27
+
28
+ def main():
29
+ """Main deployment function"""
30
+ print("πŸš€ Deploying Geetest Slider Detection API to Hugging Face Spaces...")
31
+
32
+ # Check if we're in the right directory
33
+ if not Path("app.py").exists():
34
+ print("❌ Error: app.py not found. Make sure you're in the deployment directory.")
35
+ sys.exit(1)
36
+
37
+ # Check if model file exists
38
+ if not Path("best_model.onnx").exists():
39
+ print("⚠️ Warning: best_model.onnx not found!")
40
+ print(" Make sure to upload your trained ONNX model before deployment.")
41
+ response = input(" Continue anyway? (y/N): ")
42
+ if response.lower() != 'y':
43
+ sys.exit(1)
44
+
45
+ # Get Hugging Face username and space name
46
+ hf_username = input("Enter your Hugging Face username: ")
47
+ space_name = input("Enter space name (e.g., geetest-slider-api): ")
48
+
49
+ if not hf_username or not space_name:
50
+ print("❌ Username and space name are required!")
51
+ sys.exit(1)
52
+
53
+ # Set up Git repository
54
+ print("\nπŸ“¦ Setting up Git repository...")
55
+
56
+ # Initialize git if not already done
57
+ if not Path(".git").exists():
58
+ run_command("git init")
59
+ run_command("git lfs install")
60
+
61
+ # Add files
62
+ run_command("git add .")
63
+ run_command("git commit -m 'Initial deployment of Geetest Slider Detection API'")
64
+
65
+ # Add Hugging Face remote
66
+ remote_url = f"https://huggingface.co/spaces/{hf_username}/{space_name}"
67
+ run_command(f"git remote add origin {remote_url}")
68
+
69
+ # Push to Hugging Face Spaces
70
+ print("\nπŸš€ Pushing to Hugging Face Spaces...")
71
+ if run_command("git push -u origin main"):
72
+ print(f"\nπŸŽ‰ Deployment successful!")
73
+ print(f"Your API will be available at: {remote_url}")
74
+ print(f"\nπŸ”‘ Don't forget to set your API_SECRET_KEY in the Space settings!")
75
+ print(" Go to: Space Settings > Variables and secrets")
76
+ print(" Add: API_SECRET_KEY = your-secure-api-key")
77
+ else:
78
+ # Try with master branch
79
+ print("Trying with master branch...")
80
+ run_command("git push -u origin master")
81
+
82
+ print("\nπŸ“‹ Next steps:")
83
+ print("1. Upload your trained best_model.onnx file to the Space")
84
+ print("2. Set the API_SECRET_KEY environment variable")
85
+ print("3. Make the Space private for security")
86
+ print("4. Test the API endpoints")
87
+
88
+ if __name__ == "__main__":
89
+ main()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0
3
+ pydantic==2.5.0
4
+ pillow==10.1.0
5
+ numpy==1.24.4
6
+ onnxruntime==1.18.0
7
+ pyyaml==6.0.1
8
+ python-multipart==0.0.6