asamasach commited on
Commit
26702ee
·
1 Parent(s): 2f13e47

Add unified FastAPI + Gradio service for MonitaQC integration

Browse files

- Updated app.py with combined FastAPI and Gradio interface
- FastAPI endpoints at /v1/object-detection/detect (MonitaQC compatible)
- Gradio UI at /gradio for testing and demonstration
- Support for 8 defect detection models (ONNX-based)
- CPU-only inference (no GPU required)
- Health check and model listing endpoints
- Added comprehensive API documentation (README_API.md)
- Docker support for containerized deployment
- Compatible with MonitaQC vision engine API format

This allows multiple MonitaQC vision engines to use cloud-based
defect detection while maintaining a visual testing interface.

Files changed (6) hide show
  1. Dockerfile +33 -0
  2. README_API.md +214 -0
  3. api.py +366 -0
  4. app.py +262 -49
  5. requirements-api.txt +3 -0
  6. requirements.txt +4 -1
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ libglib2.0-0 \
8
+ libsm6 \
9
+ libxext6 \
10
+ libxrender-dev \
11
+ libgomp1 \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy requirements
15
+ COPY requirements.txt requirements-api.txt ./
16
+
17
+ # Install Python dependencies
18
+ RUN pip install --no-cache-dir -r requirements.txt \
19
+ && pip install --no-cache-dir -r requirements-api.txt
20
+
21
+ # Copy application
22
+ COPY api.py ./
23
+
24
+ # Expose API port
25
+ EXPOSE 8000
26
+
27
+ # Set environment variables
28
+ ENV HOST=0.0.0.0
29
+ ENV PORT=8000
30
+ ENV DEFAULT_MODEL=data-matrix
31
+
32
+ # Run the API
33
+ CMD ["python", "api.py"]
README_API.md ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Industrial Defect Detection API
2
+
3
+ A unified service providing both FastAPI endpoints (for production) and Gradio UI (for testing/demo).
4
+
5
+ ## Overview
6
+
7
+ This service allows multiple MonitaQC vision engines to perform defect detection inference using ONNX models without requiring local GPU resources.
8
+
9
+ ### Features
10
+
11
+ - **FastAPI Endpoints**: RESTful API compatible with MonitaQC's YOLO inference format
12
+ - **Gradio UI**: Web interface for visual testing and demonstration
13
+ - **Multiple Models**: Support for 8 different defect detection models
14
+ - **CPU-based**: Runs on ONNX Runtime (no GPU required)
15
+ - **Cloud-ready**: Can be deployed to HuggingFace Spaces or any cloud platform
16
+
17
+ ## Deployment Options
18
+
19
+ ### Option 1: HuggingFace Spaces (Recommended for Cloud)
20
+
21
+ Deploy to HuggingFace Spaces to make the API publicly accessible:
22
+
23
+ 1. Create a new Space on HuggingFace:
24
+ ```bash
25
+ # Visit https://huggingface.co/new-space
26
+ # Choose: Gradio SDK
27
+ # Set to Public or Private
28
+ ```
29
+
30
+ 2. Push your code:
31
+ ```bash
32
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
33
+ cd YOUR_SPACE_NAME
34
+ cp /path/to/api.py app.py
35
+ cp /path/to/requirements*.txt .
36
+ git add .
37
+ git commit -m "Add Industrial Defect Detection API"
38
+ git push
39
+ ```
40
+
41
+ 3. Your API will be available at:
42
+ - Gradio UI: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/gradio`
43
+ - API Docs: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/docs`
44
+ - API Endpoint: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/v1/object-detection/detect`
45
+
46
+ ### Option 2: Local Docker Deployment
47
+
48
+ Run locally with Docker:
49
+
50
+ ```bash
51
+ # Build the image
52
+ docker build -t defect-detection-api .
53
+
54
+ # Run the container
55
+ docker run -p 8000:8000 \
56
+ -e HUGGINGFACE_TOKEN=your_token_here \
57
+ -e DEFAULT_MODEL=data-matrix \
58
+ defect-detection-api
59
+ ```
60
+
61
+ Access:
62
+ - Gradio UI: http://localhost:8000/gradio
63
+ - API Docs: http://localhost:8000/docs
64
+ - API Endpoint: http://localhost:8000/v1/object-detection/detect
65
+
66
+ ### Option 3: Direct Python
67
+
68
+ Run directly with Python:
69
+
70
+ ```bash
71
+ # Install dependencies
72
+ pip install -r requirements.txt -r requirements-api.txt
73
+
74
+ # Run the service
75
+ python api.py
76
+ ```
77
+
78
+ ## API Usage
79
+
80
+ ### For MonitaQC Vision Engines
81
+
82
+ Update your MonitaQC infrastructure configuration to use this API instead of local YOLO:
83
+
84
+ 1. Open MonitaQC status page: http://localhost:5050/status
85
+ 2. Go to "Infrastructure Configuration"
86
+ 3. Update YOLO URL to:
87
+ - **HuggingFace Spaces**: `https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/v1/object-detection/data-matrix/detect`
88
+ - **Local**: `http://localhost:8000/v1/object-detection/data-matrix/detect`
89
+ 4. Click "Set URL"
90
+
91
+ ### API Endpoints
92
+
93
+ #### 1. Detect with Default Model
94
+ ```bash
95
+ POST /v1/object-detection/detect
96
+ Content-Type: multipart/form-data
97
+
98
+ Parameters:
99
+ - image: file (required)
100
+ - model: string (optional, default: data-matrix)
101
+ - confidence: float (optional, default: 0.25)
102
+
103
+ Example:
104
+ curl -X POST "http://localhost:8000/v1/object-detection/detect" \
105
+ -F "image=@test.jpg" \
106
+ -F "model=data-matrix" \
107
+ -F "confidence=0.25"
108
+ ```
109
+
110
+ #### 2. Detect with Specific Model
111
+ ```bash
112
+ POST /v1/object-detection/{model_name}/detect
113
+ Content-Type: multipart/form-data
114
+
115
+ Parameters:
116
+ - image: file (required)
117
+ - confidence: float (optional, default: 0.25)
118
+
119
+ Example:
120
+ curl -X POST "http://localhost:8000/v1/object-detection/data-matrix/detect" \
121
+ -F "image=@test.jpg" \
122
+ -F "confidence=0.25"
123
+ ```
124
+
125
+ #### Response Format
126
+ ```json
127
+ [
128
+ {
129
+ "bbox": [x1, y1, x2, y2],
130
+ "confidence": 0.85,
131
+ "class_id": 0,
132
+ "x1": 100.0,
133
+ "y1": 150.0,
134
+ "x2": 200.0,
135
+ "y2": 250.0
136
+ }
137
+ ]
138
+ ```
139
+
140
+ ### Available Models
141
+
142
+ | Model Key | Model Name | Use Case |
143
+ |-----------|------------|----------|
144
+ | `data-matrix` | Data Matrix | Data Matrix code defect detection |
145
+ | `dental-implant` | Dental Implant | Dental implant defect detection |
146
+ | `ball-pen` | Ball Pen | Ball pen defect detection |
147
+ | `knit-up` | Knit Up | Knit fabric (up side) defect detection |
148
+ | `knit-back` | Knit Back | Knit fabric (back side) defect detection |
149
+ | `jean-up` | Jean Up | Jean fabric (up side) defect detection |
150
+ | `jean-back` | Jean Back | Jean fabric (back side) defect detection |
151
+ | `tire-cord` | Tire Cord | Tire cord defect detection |
152
+
153
+ ## Architecture
154
+
155
+ ```
156
+ ┌─────────────────────────────────────┐
157
+ │ HuggingFace Spaces (Cloud) │
158
+ │ │
159
+ │ ┌──────────────────────────────┐ │
160
+ │ │ FastAPI + Gradio Service │ │
161
+ │ │ │ │
162
+ │ │ - ONNX Runtime (CPU) │ │
163
+ │ │ - 8 Defect Detection Models │ │
164
+ │ │ - REST API Endpoints │ │
165
+ │ │ - Gradio Web UI │ │
166
+ │ └──────────────────────────────┘ │
167
+ └───���─────────────────────────────────┘
168
+
169
+ │ HTTPS API Calls
170
+
171
+ ┌──────────┴──────────────────────────┐
172
+ │ MonitaQC Vision Engines (Multiple) │
173
+ │ │
174
+ │ ┌────────────┐ ┌────────────┐ │
175
+ │ │ Engine 1 │ │ Engine 2 │ │
176
+ │ │ (Factory A)│ │ (Factory B)│ │
177
+ │ └────────────┘ └────────────┘ │
178
+ └─────────────────────────────────────┘
179
+ ```
180
+
181
+ ## Benefits Over Local YOLO
182
+
183
+ | Aspect | Local YOLO | HuggingFace API |
184
+ |--------|-----------|-----------------|
185
+ | **GPU Required** | Yes | No (CPU-based) |
186
+ | **Setup Complexity** | High (GPU drivers, CUDA, etc.) | Low (just API URL) |
187
+ | **Model Updates** | Manual | Automatic |
188
+ | **Multiple Factories** | Each needs GPU | All share one API |
189
+ | **Cost** | GPU hardware per site | Cloud hosting only |
190
+ | **Maintenance** | Per-site updates | Centralized |
191
+
192
+ ## Environment Variables
193
+
194
+ - `HUGGINGFACE_TOKEN`: Your HuggingFace token (for private models)
195
+ - `DEFAULT_MODEL`: Default model to use (default: data-matrix)
196
+ - `HOST`: Server host (default: 0.0.0.0)
197
+ - `PORT`: Server port (default: 8000)
198
+
199
+ ## Testing
200
+
201
+ 1. **Via Gradio UI**: Navigate to `/gradio` and upload test images
202
+ 2. **Via API**: Use curl or Python requests to test endpoints
203
+ 3. **Via Swagger**: Navigate to `/docs` for interactive API documentation
204
+
205
+ ## License
206
+
207
+ Same as the original HuggingFace models.
208
+
209
+ ## Support
210
+
211
+ For issues or questions:
212
+ - API Issues: Check `/health` endpoint
213
+ - Model Issues: Check available models at `/models`
214
+ - Integration Help: See MonitaQC documentation
api.py ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unified service with FastAPI (for MonitaQC) and Gradio (for testing/demo).
3
+ This allows multiple MonitaQC vision engines to use the API while keeping the Gradio UI accessible.
4
+ """
5
+
6
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
7
+ from fastapi.responses import JSONResponse
8
+ import gradio as gr
9
+ import onnxruntime as ort
10
+ import numpy as np
11
+ import cv2
12
+ from huggingface_hub import hf_hub_download
13
+ import os
14
+ from io import BytesIO
15
+ from typing import Optional
16
+ import logging
17
+
18
+ # Configure logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # FastAPI app
23
+ app = FastAPI(
24
+ title="Industrial Defect Detection API",
25
+ description="ONNX-based defect detection service for MonitaQC vision engines",
26
+ version="1.0.0"
27
+ )
28
+
29
+ # Available models
30
+ MODELS = {
31
+ "dental-implant": {"name": "Dental Implant", "repo": "smartfalcon-ai/Dental-Implant-Defect-Detection"},
32
+ "data-matrix": {"name": "Data Matrix", "repo": "smartfalcon-ai/Data-Matrix-Defect-Detection"},
33
+ "ball-pen": {"name": "Ball Pen", "repo": "smartfalcon-ai/Ball-Pen-Defect-Detection"},
34
+ "knit-up": {"name": "Knit Up", "repo": "smartfalcon-ai/Knit-Up-Defect-Detection"},
35
+ "knit-back": {"name": "Knit Back", "repo": "smartfalcon-ai/Knit-Back-Defect-Detection"},
36
+ "jean-back": {"name": "Jean Back", "repo": "smartfalcon-ai/Jean-Back-Defect-Detection"},
37
+ "jean-up": {"name": "Jean Up", "repo": "smartfalcon-ai/Jean-Up-Defect-Detection"},
38
+ "tire-cord": {"name": "Tire Cord", "repo": "smartfalcon-ai/Tire-Cord-Defect-Detection"}
39
+ }
40
+
41
+ # Example images for Gradio
42
+ EXAMPLES = [
43
+ # Dental Implant
44
+ ["examples/dental-implant-1.jpg", "Dental Implant", 0.25],
45
+ ["examples/dental-implant-2.jpg", "Dental Implant", 0.25],
46
+ ["examples/dental-implant-3.jpg", "Dental Implant", 0.25],
47
+ # Data Matrix
48
+ ["examples/data-matrix-1.jpg", "Data Matrix", 0.25],
49
+ ["examples/data-matrix-2.jpg", "Data Matrix", 0.25],
50
+ ["examples/data-matrix-3.jpg", "Data Matrix", 0.25],
51
+ # Ball Pen
52
+ ["examples/ball-pen-1.jpg", "Ball Pen", 0.25],
53
+ ["examples/ball-pen-2.jpg", "Ball Pen", 0.25],
54
+ ["examples/ball-pen-3.jpg", "Ball Pen", 0.25],
55
+ # Knit Up
56
+ ["examples/knit-up-1.jpg", "Knit Up", 0.25],
57
+ ["examples/knit-up-2.jpg", "Knit Up", 0.25],
58
+ ["examples/knit-up-3.jpg", "Knit Up", 0.25],
59
+ # Knit Back
60
+ ["examples/knit-back-1.jpg", "Knit Back", 0.25],
61
+ ["examples/knit-back-2.jpg", "Knit Back", 0.25],
62
+ ["examples/knit-back-3.jpg", "Knit Back", 0.25],
63
+ # Jean Back
64
+ ["examples/jean-back-1.jpg", "Jean Back", 0.25],
65
+ ["examples/jean-back-2.jpg", "Jean Back", 0.25],
66
+ ["examples/jean-back-3.jpg", "Jean Back", 0.25],
67
+ # Jean Up
68
+ ["examples/jean-up-1.jpg", "Jean Up", 0.25],
69
+ ["examples/jean-up-2.jpg", "Jean Up", 0.25],
70
+ ["examples/jean-up-3.jpg", "Jean Up", 0.25],
71
+ # Tire Cord
72
+ ["examples/tire-cord-1.jpg", "Tire Cord", 0.25],
73
+ ["examples/tire-cord-2.jpg", "Tire Cord", 0.25],
74
+ ["examples/tire-cord-3.jpg", "Tire Cord", 0.25],
75
+ ]
76
+
77
+ # Model sessions cache
78
+ sessions = {}
79
+
80
+ # Default model
81
+ DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "data-matrix")
82
+
83
+ # Inference parameters
84
+ IMG_SIZE = 640
85
+ IOU_THRESHOLD = 0.45
86
+
87
+
88
+ def get_session(model_key: str):
89
+ """Get or create ONNX inference session for a model."""
90
+ if model_key not in sessions:
91
+ if model_key not in MODELS:
92
+ raise ValueError(f"Model '{model_key}' not found. Available: {list(MODELS.keys())}")
93
+
94
+ try:
95
+ hf_token = os.environ.get("HUGGINGFACE_TOKEN", None)
96
+ repo_id = MODELS[model_key]["repo"]
97
+ logger.info(f"Downloading model: {repo_id}")
98
+ model_path = hf_hub_download(
99
+ repo_id=repo_id,
100
+ filename="best.onnx",
101
+ token=hf_token
102
+ )
103
+ sessions[model_key] = ort.InferenceSession(
104
+ model_path,
105
+ providers=["CPUExecutionProvider"]
106
+ )
107
+ logger.info(f"Model '{model_key}' loaded successfully")
108
+ except Exception as e:
109
+ logger.error(f"Failed to load model '{model_key}': {e}")
110
+ raise
111
+
112
+ return sessions[model_key]
113
+
114
+
115
+ def preprocess(img):
116
+ """Preprocess image for ONNX model."""
117
+ h, w = img.shape[:2]
118
+ img_resized = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
119
+ img_resized = img_resized.astype(np.float32) / 255.0
120
+ img_resized = img_resized.transpose(2, 0, 1)
121
+ img_resized = np.expand_dims(img_resized, 0)
122
+ return img_resized, w, h
123
+
124
+
125
+ def xywh2xyxy(x):
126
+ """Convert box format from xywh to xyxy."""
127
+ y = np.copy(x)
128
+ y[:, 0] = x[:, 0] - x[:, 2] / 2
129
+ y[:, 1] = x[:, 1] - x[:, 3] / 2
130
+ y[:, 2] = x[:, 0] + x[:, 2] / 2
131
+ y[:, 3] = x[:, 1] + x[:, 3] / 2
132
+ return y
133
+
134
+
135
+ def non_max_suppression(preds, conf_thres=0.25, iou_thres=0.45):
136
+ """Apply NMS to predictions."""
137
+ preds = preds[0]
138
+ preds = preds[preds[:, 4] > conf_thres]
139
+ if preds.shape[0] == 0:
140
+ return []
141
+
142
+ boxes = xywh2xyxy(preds[:, :4])
143
+ scores = preds[:, 4]
144
+ class_scores = preds[:, 5:]
145
+ cls_ids = np.argmax(class_scores, axis=1)
146
+ cls_conf = class_scores.max(axis=1)
147
+ final_scores = scores * cls_conf
148
+
149
+ indices = cv2.dnn.NMSBoxes(
150
+ bboxes=boxes.tolist(),
151
+ scores=final_scores.tolist(),
152
+ score_threshold=conf_thres,
153
+ nms_threshold=iou_thres
154
+ )
155
+
156
+ if len(indices) == 0:
157
+ return []
158
+
159
+ indices = indices.flatten()
160
+ output = []
161
+ for idx in indices:
162
+ x1, y1, x2, y2 = boxes[idx]
163
+ output.append({
164
+ "bbox": [float(x1), float(y1), float(x2), float(y2)],
165
+ "confidence": float(final_scores[idx]),
166
+ "class_id": int(cls_ids[idx]),
167
+ "x1": float(x1),
168
+ "y1": float(y1),
169
+ "x2": float(x2),
170
+ "y2": float(y2)
171
+ })
172
+ return output
173
+
174
+
175
+ # ============================================================================
176
+ # FastAPI Endpoints (for MonitaQC vision engines)
177
+ # ============================================================================
178
+
179
+ @app.get("/")
180
+ async def root():
181
+ """API root endpoint."""
182
+ return {
183
+ "service": "Industrial Defect Detection API",
184
+ "version": "1.0.0",
185
+ "endpoints": {
186
+ "api": "/docs",
187
+ "gradio": "/gradio"
188
+ },
189
+ "models": list(MODELS.keys()),
190
+ "default_model": DEFAULT_MODEL
191
+ }
192
+
193
+
194
+ @app.get("/health")
195
+ async def health_check():
196
+ """Health check endpoint."""
197
+ return {"status": "healthy", "models_loaded": len(sessions)}
198
+
199
+
200
+ @app.get("/models")
201
+ async def list_models():
202
+ """List all available models."""
203
+ return {
204
+ "models": {k: v["name"] for k, v in MODELS.items()},
205
+ "loaded": list(sessions.keys())
206
+ }
207
+
208
+
209
+ @app.post("/v1/object-detection/detect")
210
+ async def detect_defects(
211
+ image: UploadFile = File(...),
212
+ model: Optional[str] = Form(DEFAULT_MODEL),
213
+ confidence: Optional[float] = Form(0.25)
214
+ ):
215
+ """
216
+ Detect defects in an uploaded image.
217
+
218
+ Compatible with MonitaQC's YOLO inference API format.
219
+
220
+ Args:
221
+ image: Image file to analyze
222
+ model: Model name to use (default: data-matrix)
223
+ confidence: Confidence threshold (default: 0.25)
224
+
225
+ Returns:
226
+ JSON array of detections with bbox, confidence, and class_id
227
+ """
228
+ try:
229
+ # Read image from upload
230
+ contents = await image.read()
231
+ nparr = np.frombuffer(contents, np.uint8)
232
+ img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
233
+
234
+ if img_bgr is None:
235
+ raise HTTPException(status_code=400, detail="Invalid image file")
236
+
237
+ # Get model session
238
+ session = get_session(model)
239
+
240
+ # Preprocess
241
+ blob, orig_w, orig_h = preprocess(img_bgr)
242
+
243
+ # Run inference
244
+ preds = session.run(None, {"images": blob})[0]
245
+
246
+ # Post-process
247
+ detections = non_max_suppression(preds, confidence, IOU_THRESHOLD)
248
+
249
+ # Scale bboxes back to original image size
250
+ for det in detections:
251
+ det["bbox"][0] = det["bbox"][0] / IMG_SIZE * orig_w
252
+ det["bbox"][1] = det["bbox"][1] / IMG_SIZE * orig_h
253
+ det["bbox"][2] = det["bbox"][2] / IMG_SIZE * orig_w
254
+ det["bbox"][3] = det["bbox"][3] / IMG_SIZE * orig_h
255
+ det["x1"] = det["bbox"][0]
256
+ det["y1"] = det["bbox"][1]
257
+ det["x2"] = det["bbox"][2]
258
+ det["y2"] = det["bbox"][3]
259
+
260
+ logger.info(f"Processed image with model '{model}': {len(detections)} detections")
261
+
262
+ return detections
263
+
264
+ except Exception as e:
265
+ logger.error(f"Detection error: {e}")
266
+ raise HTTPException(status_code=500, detail=str(e))
267
+
268
+
269
+ @app.post("/v1/object-detection/{model_name}/detect")
270
+ async def detect_defects_with_model(
271
+ model_name: str,
272
+ image: UploadFile = File(...),
273
+ confidence: Optional[float] = Form(0.25)
274
+ ):
275
+ """
276
+ Detect defects using a specific model (path parameter).
277
+
278
+ This endpoint is compatible with MonitaQC's current YOLO API format.
279
+
280
+ Args:
281
+ model_name: Model to use (e.g., 'data-matrix', 'dental-implant')
282
+ image: Image file to analyze
283
+ confidence: Confidence threshold (default: 0.25)
284
+
285
+ Returns:
286
+ JSON array of detections
287
+ """
288
+ return await detect_defects(image, model_name, confidence)
289
+
290
+
291
+ # ============================================================================
292
+ # Gradio Interface (for testing/demo)
293
+ # ============================================================================
294
+
295
+ def gradio_inference(image, model_display_name, conf_threshold):
296
+ """Inference function for Gradio UI."""
297
+ # Find model key from display name
298
+ model_key = None
299
+ for key, val in MODELS.items():
300
+ if val["name"] == model_display_name:
301
+ model_key = key
302
+ break
303
+
304
+ if model_key is None:
305
+ return image
306
+
307
+ session = get_session(model_key)
308
+ img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
309
+ blob, orig_w, orig_h = preprocess(img_bgr)
310
+ preds = session.run(None, {"images": blob})[0]
311
+ detections = non_max_suppression(preds, conf_threshold, IOU_THRESHOLD)
312
+
313
+ for det in detections:
314
+ x1 = int(det["x1"] / IMG_SIZE * orig_w)
315
+ y1 = int(det["y1"] / IMG_SIZE * orig_h)
316
+ x2 = int(det["x2"] / IMG_SIZE * orig_w)
317
+ y2 = int(det["y2"] / IMG_SIZE * orig_h)
318
+ score = det["confidence"]
319
+ cls_id = det["class_id"]
320
+
321
+ label = f"{cls_id}:{score:.2f}"
322
+ cv2.rectangle(img_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2)
323
+ cv2.putText(img_bgr, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
324
+
325
+ return cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
326
+
327
+
328
+ # Create Gradio interface
329
+ gradio_app = gr.Interface(
330
+ fn=gradio_inference,
331
+ inputs=[
332
+ gr.Image(type="numpy"),
333
+ gr.Dropdown([v["name"] for v in MODELS.values()], label="Select Model"),
334
+ gr.Slider(minimum=0.0, maximum=1.0, value=0.25, step=0.01, label="Confidence Threshold")
335
+ ],
336
+ outputs=gr.Image(type="numpy"),
337
+ title="Industrial Defect Detection - Testing Interface",
338
+ description="""
339
+ **Testing Interface** for Industrial Defect Detection models.
340
+
341
+ - **For Production Use:** Use the FastAPI endpoints at `/v1/object-detection/detect`
342
+ - **For Testing:** Use this Gradio interface to visually inspect results
343
+
344
+ Upload an image, select a defect model, and adjust the confidence threshold.
345
+ You can also choose from the samples at the bottom of the page.
346
+ """,
347
+ examples=EXAMPLES,
348
+ examples_per_page=24,
349
+ )
350
+
351
+ # Mount Gradio app to FastAPI
352
+ app = gr.mount_gradio_app(app, gradio_app, path="/gradio")
353
+
354
+
355
+ if __name__ == "__main__":
356
+ import uvicorn
357
+ port = int(os.environ.get("PORT", 8000))
358
+ host = os.environ.get("HOST", "0.0.0.0")
359
+
360
+ logger.info(f"Starting Industrial Defect Detection Service on {host}:{port}")
361
+ logger.info(f" - FastAPI docs: http://{host}:{port}/docs")
362
+ logger.info(f" - Gradio UI: http://{host}:{port}/gradio")
363
+ logger.info(f"Available models: {list(MODELS.keys())}")
364
+ logger.info(f"Default model: {DEFAULT_MODEL}")
365
+
366
+ uvicorn.run(app, host=host, port=port)
app.py CHANGED
@@ -1,23 +1,44 @@
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import onnxruntime as ort
3
  import numpy as np
4
  import cv2
5
  from huggingface_hub import hf_hub_download
6
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- # Private models
9
  MODELS = {
10
- "Dental Implant": "smartfalcon-ai/Dental-Implant-Defect-Detection",
11
- "Data Matrix": "smartfalcon-ai/Data-Matrix-Defect-Detection",
12
- "Ball Pen": "smartfalcon-ai/Ball-Pen-Defect-Detection",
13
- "Knit Up": "smartfalcon-ai/Knit-Up-Defect-Detection",
14
- "Knit Back": "smartfalcon-ai/Knit-Back-Defect-Detection",
15
- "Jean Back": "smartfalcon-ai/Jean-Back-Defect-Detection",
16
- "Jean Up": "smartfalcon-ai/Jean-Up-Defect-Detection",
17
- "Tire Cord": "smartfalcon-ai/Tire-Cord-Defect-Detection"
18
  }
19
 
20
- # Example images for each model (3 examples per task)
21
  EXAMPLES = [
22
  # Dental Implant
23
  ["examples/dental-implant-1.jpg", "Dental Implant", 0.25],
@@ -53,23 +74,46 @@ EXAMPLES = [
53
  ["examples/tire-cord-3.jpg", "Tire Cord", 0.25],
54
  ]
55
 
 
56
  sessions = {}
57
 
58
- def get_session(model_name):
59
- if model_name not in sessions:
60
- hf_token = os.environ.get("HUGGINGFACE_TOKEN", None)
61
- model_path = hf_hub_download(
62
- repo_id=MODELS[model_name],
63
- filename="best.onnx",
64
- token=hf_token
65
- )
66
- sessions[model_name] = ort.InferenceSession(model_path, providers=["CPUExecutionProvider"])
67
- return sessions[model_name]
68
 
 
69
  IMG_SIZE = 640
70
  IOU_THRESHOLD = 0.45
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  def preprocess(img):
 
73
  h, w = img.shape[:2]
74
  img_resized = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
75
  img_resized = img_resized.astype(np.float32) / 255.0
@@ -77,23 +121,27 @@ def preprocess(img):
77
  img_resized = np.expand_dims(img_resized, 0)
78
  return img_resized, w, h
79
 
 
80
  def xywh2xyxy(x):
 
81
  y = np.copy(x)
82
- y[:,0] = x[:,0] - x[:,2]/2
83
- y[:,1] = x[:,1] - x[:,3]/2
84
- y[:,2] = x[:,0] + x[:,2]/2
85
- y[:,3] = x[:,1] + x[:,3]/2
86
  return y
87
 
 
88
  def non_max_suppression(preds, conf_thres=0.25, iou_thres=0.45):
 
89
  preds = preds[0]
90
- preds = preds[preds[:,4] > conf_thres]
91
- if preds.shape[0]==0:
92
  return []
93
 
94
  boxes = xywh2xyxy(preds[:, :4])
95
- scores = preds[:,4]
96
- class_scores = preds[:,5:]
97
  cls_ids = np.argmax(class_scores, axis=1)
98
  cls_conf = class_scores.max(axis=1)
99
  final_scores = scores * cls_conf
@@ -104,50 +152,215 @@ def non_max_suppression(preds, conf_thres=0.25, iou_thres=0.45):
104
  score_threshold=conf_thres,
105
  nms_threshold=iou_thres
106
  )
107
- if len(indices)==0:
 
108
  return []
109
 
110
  indices = indices.flatten()
111
- output=[]
112
  for idx in indices:
113
  x1, y1, x2, y2 = boxes[idx]
114
- output.append([x1, y1, x2, y2, final_scores[idx], cls_ids[idx]])
 
 
 
 
 
 
 
 
115
  return output
116
 
117
- def infer(image, model_name, conf_threshold):
118
- session = get_session(model_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
120
  blob, orig_w, orig_h = preprocess(img_bgr)
121
  preds = session.run(None, {"images": blob})[0]
122
  detections = non_max_suppression(preds, conf_threshold, IOU_THRESHOLD)
123
 
124
  for det in detections:
125
- x1, y1, x2, y2, score, cls_id = det
126
- x1 = int(x1 / IMG_SIZE * orig_w)
127
- y1 = int(y1 / IMG_SIZE * orig_h)
128
- x2 = int(x2 / IMG_SIZE * orig_w)
129
- y2 = int(y2 / IMG_SIZE * orig_h)
 
130
 
131
- label = f"{int(cls_id)}:{score:.2f}"
132
- cv2.rectangle(img_bgr, (x1, y1), (x2, y2), (0,255,0), 2)
133
- cv2.putText(img_bgr, label, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
134
 
135
  return cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
136
 
137
- # Gradio interface
138
- demo = gr.Interface(
139
- fn=infer,
 
140
  inputs=[
141
  gr.Image(type="numpy"),
142
- gr.Dropdown(list(MODELS.keys()), label="Select Model"),
143
- gr.Slider(minimum=0.0, maximum=1.0, value=0.001, step=0.01, label="Confidence Threshold")
144
  ],
145
  outputs=gr.Image(type="numpy"),
146
- title="Industrial Defect Detection",
147
- description="**Warning:** This application is only for testing and demonstrating how the AI models work. The production-ready AI models are slightly different from those in this application.\n\nUpload an image, select a defect model, and adjust the confidence threshold.\n\nYou can also choose from the samples at the bottom of the page.",
 
 
 
 
 
 
 
 
148
  examples=EXAMPLES,
149
  examples_per_page=24,
150
  )
151
 
 
 
 
 
152
  if __name__ == "__main__":
153
- demo.launch(allowed_paths=["examples"])
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unified service with FastAPI (for MonitaQC) and Gradio (for testing/demo).
3
+ This allows multiple MonitaQC vision engines to use the API while keeping the Gradio UI accessible.
4
+ """
5
+
6
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
7
+ from fastapi.responses import JSONResponse
8
  import gradio as gr
9
  import onnxruntime as ort
10
  import numpy as np
11
  import cv2
12
  from huggingface_hub import hf_hub_download
13
  import os
14
+ from io import BytesIO
15
+ from typing import Optional
16
+ import logging
17
+
18
+ # Configure logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # FastAPI app
23
+ app = FastAPI(
24
+ title="Industrial Defect Detection API",
25
+ description="ONNX-based defect detection service for MonitaQC vision engines",
26
+ version="1.0.0"
27
+ )
28
 
29
+ # Available models
30
  MODELS = {
31
+ "dental-implant": {"name": "Dental Implant", "repo": "smartfalcon-ai/Dental-Implant-Defect-Detection"},
32
+ "data-matrix": {"name": "Data Matrix", "repo": "smartfalcon-ai/Data-Matrix-Defect-Detection"},
33
+ "ball-pen": {"name": "Ball Pen", "repo": "smartfalcon-ai/Ball-Pen-Defect-Detection"},
34
+ "knit-up": {"name": "Knit Up", "repo": "smartfalcon-ai/Knit-Up-Defect-Detection"},
35
+ "knit-back": {"name": "Knit Back", "repo": "smartfalcon-ai/Knit-Back-Defect-Detection"},
36
+ "jean-back": {"name": "Jean Back", "repo": "smartfalcon-ai/Jean-Back-Defect-Detection"},
37
+ "jean-up": {"name": "Jean Up", "repo": "smartfalcon-ai/Jean-Up-Defect-Detection"},
38
+ "tire-cord": {"name": "Tire Cord", "repo": "smartfalcon-ai/Tire-Cord-Defect-Detection"}
39
  }
40
 
41
+ # Example images for Gradio
42
  EXAMPLES = [
43
  # Dental Implant
44
  ["examples/dental-implant-1.jpg", "Dental Implant", 0.25],
 
74
  ["examples/tire-cord-3.jpg", "Tire Cord", 0.25],
75
  ]
76
 
77
+ # Model sessions cache
78
  sessions = {}
79
 
80
+ # Default model
81
+ DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "data-matrix")
 
 
 
 
 
 
 
 
82
 
83
+ # Inference parameters
84
  IMG_SIZE = 640
85
  IOU_THRESHOLD = 0.45
86
 
87
+
88
+ def get_session(model_key: str):
89
+ """Get or create ONNX inference session for a model."""
90
+ if model_key not in sessions:
91
+ if model_key not in MODELS:
92
+ raise ValueError(f"Model '{model_key}' not found. Available: {list(MODELS.keys())}")
93
+
94
+ try:
95
+ hf_token = os.environ.get("HUGGINGFACE_TOKEN", None)
96
+ repo_id = MODELS[model_key]["repo"]
97
+ logger.info(f"Downloading model: {repo_id}")
98
+ model_path = hf_hub_download(
99
+ repo_id=repo_id,
100
+ filename="best.onnx",
101
+ token=hf_token
102
+ )
103
+ sessions[model_key] = ort.InferenceSession(
104
+ model_path,
105
+ providers=["CPUExecutionProvider"]
106
+ )
107
+ logger.info(f"Model '{model_key}' loaded successfully")
108
+ except Exception as e:
109
+ logger.error(f"Failed to load model '{model_key}': {e}")
110
+ raise
111
+
112
+ return sessions[model_key]
113
+
114
+
115
  def preprocess(img):
116
+ """Preprocess image for ONNX model."""
117
  h, w = img.shape[:2]
118
  img_resized = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
119
  img_resized = img_resized.astype(np.float32) / 255.0
 
121
  img_resized = np.expand_dims(img_resized, 0)
122
  return img_resized, w, h
123
 
124
+
125
  def xywh2xyxy(x):
126
+ """Convert box format from xywh to xyxy."""
127
  y = np.copy(x)
128
+ y[:, 0] = x[:, 0] - x[:, 2] / 2
129
+ y[:, 1] = x[:, 1] - x[:, 3] / 2
130
+ y[:, 2] = x[:, 0] + x[:, 2] / 2
131
+ y[:, 3] = x[:, 1] + x[:, 3] / 2
132
  return y
133
 
134
+
135
  def non_max_suppression(preds, conf_thres=0.25, iou_thres=0.45):
136
+ """Apply NMS to predictions."""
137
  preds = preds[0]
138
+ preds = preds[preds[:, 4] > conf_thres]
139
+ if preds.shape[0] == 0:
140
  return []
141
 
142
  boxes = xywh2xyxy(preds[:, :4])
143
+ scores = preds[:, 4]
144
+ class_scores = preds[:, 5:]
145
  cls_ids = np.argmax(class_scores, axis=1)
146
  cls_conf = class_scores.max(axis=1)
147
  final_scores = scores * cls_conf
 
152
  score_threshold=conf_thres,
153
  nms_threshold=iou_thres
154
  )
155
+
156
+ if len(indices) == 0:
157
  return []
158
 
159
  indices = indices.flatten()
160
+ output = []
161
  for idx in indices:
162
  x1, y1, x2, y2 = boxes[idx]
163
+ output.append({
164
+ "bbox": [float(x1), float(y1), float(x2), float(y2)],
165
+ "confidence": float(final_scores[idx]),
166
+ "class_id": int(cls_ids[idx]),
167
+ "x1": float(x1),
168
+ "y1": float(y1),
169
+ "x2": float(x2),
170
+ "y2": float(y2)
171
+ })
172
  return output
173
 
174
+
175
+ # ============================================================================
176
+ # FastAPI Endpoints (for MonitaQC vision engines)
177
+ # ============================================================================
178
+
179
+ @app.get("/")
180
+ async def root():
181
+ """API root endpoint."""
182
+ return {
183
+ "service": "Industrial Defect Detection API",
184
+ "version": "1.0.0",
185
+ "endpoints": {
186
+ "api": "/docs",
187
+ "gradio": "/gradio"
188
+ },
189
+ "models": list(MODELS.keys()),
190
+ "default_model": DEFAULT_MODEL
191
+ }
192
+
193
+
194
+ @app.get("/health")
195
+ async def health_check():
196
+ """Health check endpoint."""
197
+ return {"status": "healthy", "models_loaded": len(sessions)}
198
+
199
+
200
+ @app.get("/models")
201
+ async def list_models():
202
+ """List all available models."""
203
+ return {
204
+ "models": {k: v["name"] for k, v in MODELS.items()},
205
+ "loaded": list(sessions.keys())
206
+ }
207
+
208
+
209
+ @app.post("/v1/object-detection/detect")
210
+ async def detect_defects(
211
+ image: UploadFile = File(...),
212
+ model: Optional[str] = Form(DEFAULT_MODEL),
213
+ confidence: Optional[float] = Form(0.25)
214
+ ):
215
+ """
216
+ Detect defects in an uploaded image.
217
+
218
+ Compatible with MonitaQC's YOLO inference API format.
219
+
220
+ Args:
221
+ image: Image file to analyze
222
+ model: Model name to use (default: data-matrix)
223
+ confidence: Confidence threshold (default: 0.25)
224
+
225
+ Returns:
226
+ JSON array of detections with bbox, confidence, and class_id
227
+ """
228
+ try:
229
+ # Read image from upload
230
+ contents = await image.read()
231
+ nparr = np.frombuffer(contents, np.uint8)
232
+ img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
233
+
234
+ if img_bgr is None:
235
+ raise HTTPException(status_code=400, detail="Invalid image file")
236
+
237
+ # Get model session
238
+ session = get_session(model)
239
+
240
+ # Preprocess
241
+ blob, orig_w, orig_h = preprocess(img_bgr)
242
+
243
+ # Run inference
244
+ preds = session.run(None, {"images": blob})[0]
245
+
246
+ # Post-process
247
+ detections = non_max_suppression(preds, confidence, IOU_THRESHOLD)
248
+
249
+ # Scale bboxes back to original image size
250
+ for det in detections:
251
+ det["bbox"][0] = det["bbox"][0] / IMG_SIZE * orig_w
252
+ det["bbox"][1] = det["bbox"][1] / IMG_SIZE * orig_h
253
+ det["bbox"][2] = det["bbox"][2] / IMG_SIZE * orig_w
254
+ det["bbox"][3] = det["bbox"][3] / IMG_SIZE * orig_h
255
+ det["x1"] = det["bbox"][0]
256
+ det["y1"] = det["bbox"][1]
257
+ det["x2"] = det["bbox"][2]
258
+ det["y2"] = det["bbox"][3]
259
+
260
+ logger.info(f"Processed image with model '{model}': {len(detections)} detections")
261
+
262
+ return detections
263
+
264
+ except Exception as e:
265
+ logger.error(f"Detection error: {e}")
266
+ raise HTTPException(status_code=500, detail=str(e))
267
+
268
+
269
+ @app.post("/v1/object-detection/{model_name}/detect")
270
+ async def detect_defects_with_model(
271
+ model_name: str,
272
+ image: UploadFile = File(...),
273
+ confidence: Optional[float] = Form(0.25)
274
+ ):
275
+ """
276
+ Detect defects using a specific model (path parameter).
277
+
278
+ This endpoint is compatible with MonitaQC's current YOLO API format.
279
+
280
+ Args:
281
+ model_name: Model to use (e.g., 'data-matrix', 'dental-implant')
282
+ image: Image file to analyze
283
+ confidence: Confidence threshold (default: 0.25)
284
+
285
+ Returns:
286
+ JSON array of detections
287
+ """
288
+ return await detect_defects(image, model_name, confidence)
289
+
290
+
291
+ # ============================================================================
292
+ # Gradio Interface (for testing/demo)
293
+ # ============================================================================
294
+
295
+ def gradio_inference(image, model_display_name, conf_threshold):
296
+ """Inference function for Gradio UI."""
297
+ # Find model key from display name
298
+ model_key = None
299
+ for key, val in MODELS.items():
300
+ if val["name"] == model_display_name:
301
+ model_key = key
302
+ break
303
+
304
+ if model_key is None:
305
+ return image
306
+
307
+ session = get_session(model_key)
308
  img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
309
  blob, orig_w, orig_h = preprocess(img_bgr)
310
  preds = session.run(None, {"images": blob})[0]
311
  detections = non_max_suppression(preds, conf_threshold, IOU_THRESHOLD)
312
 
313
  for det in detections:
314
+ x1 = int(det["x1"] / IMG_SIZE * orig_w)
315
+ y1 = int(det["y1"] / IMG_SIZE * orig_h)
316
+ x2 = int(det["x2"] / IMG_SIZE * orig_w)
317
+ y2 = int(det["y2"] / IMG_SIZE * orig_h)
318
+ score = det["confidence"]
319
+ cls_id = det["class_id"]
320
 
321
+ label = f"{cls_id}:{score:.2f}"
322
+ cv2.rectangle(img_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2)
323
+ cv2.putText(img_bgr, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
324
 
325
  return cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
326
 
327
+
328
+ # Create Gradio interface
329
+ gradio_app = gr.Interface(
330
+ fn=gradio_inference,
331
  inputs=[
332
  gr.Image(type="numpy"),
333
+ gr.Dropdown([v["name"] for v in MODELS.values()], label="Select Model"),
334
+ gr.Slider(minimum=0.0, maximum=1.0, value=0.25, step=0.01, label="Confidence Threshold")
335
  ],
336
  outputs=gr.Image(type="numpy"),
337
+ title="Industrial Defect Detection - Testing Interface",
338
+ description="""
339
+ **Testing Interface** for Industrial Defect Detection models.
340
+
341
+ - **For Production Use:** Use the FastAPI endpoints at `/v1/object-detection/detect`
342
+ - **For Testing:** Use this Gradio interface to visually inspect results
343
+
344
+ Upload an image, select a defect model, and adjust the confidence threshold.
345
+ You can also choose from the samples at the bottom of the page.
346
+ """,
347
  examples=EXAMPLES,
348
  examples_per_page=24,
349
  )
350
 
351
+ # Mount Gradio app to FastAPI
352
+ app = gr.mount_gradio_app(app, gradio_app, path="/gradio")
353
+
354
+
355
  if __name__ == "__main__":
356
+ import uvicorn
357
+ port = int(os.environ.get("PORT", 7860)) # HuggingFace Spaces uses 7860
358
+ host = os.environ.get("HOST", "0.0.0.0")
359
+
360
+ logger.info(f"Starting Industrial Defect Detection Service on {host}:{port}")
361
+ logger.info(f" - FastAPI docs: http://{host}:{port}/docs")
362
+ logger.info(f" - Gradio UI: http://{host}:{port}/gradio")
363
+ logger.info(f"Available models: {list(MODELS.keys())}")
364
+ logger.info(f"Default model: {DEFAULT_MODEL}")
365
+
366
+ uvicorn.run(app, host=host, port=port)
requirements-api.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-multipart
requirements.txt CHANGED
@@ -2,4 +2,7 @@ gradio
2
  onnxruntime
3
  numpy
4
  opencv-python
5
- huggingface_hub
 
 
 
 
2
  onnxruntime
3
  numpy
4
  opencv-python
5
+ huggingface_hub
6
+ fastapi
7
+ uvicorn[standard]
8
+ python-multipart