Hugging Face uploader commited on
Commit
f86cd16
·
1 Parent(s): acae32a

Upload cleaned source files

Browse files
.gitattributes DELETED
@@ -1,35 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md DELETED
@@ -1,12 +0,0 @@
1
- ---
2
- title: Elite 17
3
- emoji: 🐢
4
- colorFrom: purple
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 6.1.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,297 +1,285 @@
1
- import cv2
2
- import numpy as np
3
- import json
4
  import os
5
- from pathlib import Path
6
- from fastapi import FastAPI, File, UploadFile, Form, HTTPException
 
 
 
 
 
 
 
 
 
7
  from fastapi.responses import JSONResponse
8
- from typing import Dict, Any, Tuple, Optional, Union
9
- import io
10
- import aiohttp
11
  import uvicorn
12
- from urllib.parse import urlparse
13
-
14
- # --- Original Cursor Detection Functions (Adapted for Server) ---
15
-
16
- def to_rgb(img: np.ndarray) -> Optional[np.ndarray]:
17
- """Converts image to BGR format (3 channels). Handles None input."""
18
- if img is None:
19
- return None
20
- if len(img.shape) == 2:
21
- # Grayscale to BGR
22
- return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
23
- if img.shape[2] == 4:
24
- # BGRA to BGR (removes alpha channel)
25
- return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
26
- # Already BGR or RGB (assuming OpenCV reads as BGR)
27
- return img
28
 
29
- def get_mask_from_alpha(template_img: np.ndarray) -> Optional[np.ndarray]:
30
- """Extracts a mask from the alpha channel of a 4-channel image."""
31
- if template_img is not None and len(template_img.shape) == 3 and template_img.shape[2] == 4:
32
- # Create a mask where alpha is greater than 0
33
- return (template_img[:, :, 3] > 0).astype(np.uint8) * 255
34
- return None
35
-
36
- def detect_cursor_in_frame_multi(
37
- frame: np.ndarray,
38
- cursor_templates: Dict[str, np.ndarray],
39
- threshold: float = 0.8
40
- ) -> Tuple[Optional[Tuple[int, int]], float, Optional[str]]:
41
- """
42
- Detects the best matching cursor template in a single frame.
43
- Returns (position, confidence, template_name).
44
- """
45
- best_pos = None
46
- best_conf = -1.0
47
- best_template_name = None
48
- frame_rgb = to_rgb(frame)
49
-
50
- if frame_rgb is None:
51
- return None, -1.0, None
52
 
53
- for template_name, cursor_template in cursor_templates.items():
54
- template_rgb = to_rgb(cursor_template)
55
- mask = get_mask_from_alpha(cursor_template)
 
 
 
 
 
56
 
57
- if template_rgb is None or template_rgb.shape[2] != frame_rgb.shape[2]:
58
- # print(f"[WARN] Skipping template {template_name} due to channel mismatch or load error.")
59
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- # Ensure template is smaller than or equal to the frame
62
- if template_rgb.shape[0] > frame_rgb.shape[0] or template_rgb.shape[1] > frame_rgb.shape[1]:
63
- # print(f"[WARN] Skipping template {template_name}: template larger than frame.")
64
- continue
 
 
 
 
 
 
 
65
 
66
- try:
67
- # Match template. Use mask for non-rectangular templates.
68
- result = cv2.matchTemplate(frame_rgb, template_rgb, cv2.TM_CCOEFF_NORMED, mask=mask)
69
- except Exception as e:
70
- # print(f"[WARN] matchTemplate failed for {template_name}: {e}")
71
- continue
72
 
73
- _, max_val, _, max_loc = cv2.minMaxLoc(result)
 
74
 
75
- if max_val > best_conf:
76
- best_conf = max_val
77
- if max_val >= threshold:
78
- cursor_w, cursor_h = template_rgb.shape[1], template_rgb.shape[0]
79
- # Calculate center position of the detected area
80
- cursor_x = max_loc[0] + cursor_w // 2
81
- cursor_y = max_loc[1] + cursor_h // 2
82
- best_pos = (cursor_x, cursor_y)
83
- best_template_name = template_name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- if best_conf >= threshold:
86
- return best_pos, best_conf, best_template_name
87
- return None, best_conf, None
88
 
89
- async def download_image_from_url(url: str) -> bytes:
90
- """Download image from URL and return as bytes."""
91
- async with aiohttp.ClientSession() as session:
92
- async with session.get(url) as response:
93
- if response.status != 200:
94
- raise HTTPException(
95
- status_code=400,
96
- detail=f"Failed to fetch image from URL. Status code: {response.status}"
97
- )
98
- return await response.read()
99
 
100
- # --- Server Setup ---
 
 
 
 
 
101
 
 
102
  app = FastAPI(
103
- title="Cursor Tracker API",
104
- description="API to detect and track mouse cursors in uploaded images using template matching."
105
  )
106
 
107
- # Global variable to store loaded templates
108
- CURSOR_TEMPLATES: Dict[str, np.ndarray] = {}
109
- CURSOR_TEMPLATES_DIR = Path("cursors")
110
-
111
- def load_cursor_templates():
112
- """Loads all cursor templates from the specified directory."""
113
- global CURSOR_TEMPLATES
114
- if CURSOR_TEMPLATES:
115
- print("Templates already loaded.")
116
- return
117
-
118
- print(f"Loading cursor templates from: {CURSOR_TEMPLATES_DIR}")
119
-
120
- if not CURSOR_TEMPLATES_DIR.is_dir():
121
- print(f"Error: Template directory not found at {CURSOR_TEMPLATES_DIR}")
122
- return
123
-
124
- for template_file in CURSOR_TEMPLATES_DIR.glob('*.png'):
125
- # Load image with alpha channel (IMREAD_UNCHANGED)
126
- template_img = cv2.imread(str(template_file), cv2.IMREAD_UNCHANGED)
127
- if template_img is not None:
128
- CURSOR_TEMPLATES[template_file.name] = template_img
129
- else:
130
- print(f"[WARN] Could not load template: {template_file.name}")
131
 
132
- if not CURSOR_TEMPLATES:
133
- print(f"FATAL: No cursor templates found in: {CURSOR_TEMPLATES_DIR}")
134
- else:
135
- print(f"Successfully loaded {len(CURSOR_TEMPLATES)} templates.")
 
 
 
136
 
137
- @app.on_event("startup")
138
- async def startup_event():
139
- """Load templates when the application starts."""
140
- load_cursor_templates()
141
 
142
  @app.get("/")
143
  async def root():
144
- """Simple root endpoint for health check."""
145
- return {"message": "Cursor Tracker API is running. Use /track_cursor to upload an image."}
146
-
147
- @app.post("/track_cursor")
148
- async def track_cursor_endpoint(
149
- file: UploadFile = File(...),
150
- threshold: float = Form(0.8)
151
- ):
152
- """
153
- Accepts an image file and returns the detected cursor position and details.
154
- """
155
- if not CURSOR_TEMPLATES:
156
- raise HTTPException(
157
- status_code=503,
158
- detail="Cursor templates are not loaded. Server initialization failed."
159
- )
160
-
161
- # 1. Read image file content
162
- content = await file.read()
 
 
 
 
 
 
163
 
164
- # 2. Convert file content to OpenCV image format
165
- np_array = np.frombuffer(content, np.uint8)
166
- frame = cv2.imdecode(np_array, cv2.IMREAD_UNCHANGED)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- if frame is None:
169
- raise HTTPException(
170
- status_code=400,
171
- detail="Could not decode image file. Ensure it is a valid image format (e.g., PNG, JPEG)."
 
 
172
  )
173
 
174
- # 3. Detect cursor
175
- pos, conf, template_name = detect_cursor_in_frame_multi(frame, CURSOR_TEMPLATES, threshold)
176
-
177
- # 4. Log values for debugging
178
- print(f"pos: {pos}, type: {type(pos)}")
179
- print(f"conf: {conf}, type: {type(conf)}")
180
- print(f"template_name: {template_name}, type: {type(template_name)}")
181
- print(f"frame.shape: {frame.shape}, type: {type(frame.shape)}")
182
-
183
- # 5. Prepare response
184
- # Handle infinite confidence values
185
- confidence = float(conf)
186
- if not (confidence == float('inf') or confidence == float('-inf')):
187
- confidence_val = confidence
188
- else:
189
- confidence_val = 1.0 if confidence > 0 else 0.0
190
-
191
- if pos is not None:
192
- response_data = {
193
- 'cursor_active': True,
194
- 'x': pos[0],
195
- 'y': pos[1],
196
- 'confidence': confidence_val,
197
- 'template': template_name,
198
- 'image_shape': list(frame.shape)
199
- }
200
- else:
201
- response_data = {
202
- 'cursor_active': False,
203
- 'x': None,
204
- 'y': None,
205
- 'confidence': confidence_val,
206
- 'template': None,
207
- 'image_shape': list(frame.shape)
208
- }
209
-
210
- return JSONResponse(content=response_data)
211
-
212
- # Optional: Endpoint to get a list of loaded templates
213
- @app.post("/track_cursor_url")
214
- async def track_cursor_url_endpoint(
215
- image_url: str = Form(...),
216
- threshold: float = Form(0.8)
217
- ):
218
- """
219
- Accepts an image URL and returns the detected cursor position and details.
220
  """
221
- if not CURSOR_TEMPLATES:
222
- raise HTTPException(
223
- status_code=503,
224
- detail="Cursor templates are not loaded. Server initialization failed."
225
- )
226
-
227
  try:
228
- # Validate URL
229
- parsed_url = urlparse(image_url)
230
- if not all([parsed_url.scheme, parsed_url.netloc]):
231
- raise HTTPException(
232
- status_code=400,
233
- detail="Invalid URL provided"
234
- )
235
-
236
- # Download image
237
- content = await download_image_from_url(image_url)
238
 
239
- # Convert to OpenCV format
240
- np_array = np.frombuffer(content, np.uint8)
241
- frame = cv2.imdecode(np_array, cv2.IMREAD_UNCHANGED)
242
 
243
- if frame is None:
244
- raise HTTPException(
245
- status_code=400,
246
- detail="Could not decode image from URL. Ensure it is a valid image format (e.g., PNG, JPEG)."
247
- )
248
-
249
- # Detect cursor
250
- pos, conf, template_name = detect_cursor_in_frame_multi(frame, CURSOR_TEMPLATES, threshold)
251
-
252
- # Prepare response
253
- if pos is not None:
254
- response_data = {
255
- 'cursor_active': True,
256
- 'x': pos[0],
257
- 'y': pos[1],
258
- 'confidence': float(conf),
259
- 'template': template_name,
260
- 'image_shape': list(frame.shape),
261
- 'source_url': image_url
262
- }
263
- else:
264
- response_data = {
265
- 'cursor_active': False,
266
- 'x': None,
267
- 'y': None,
268
- 'confidence': float(conf),
269
- 'template': None,
270
- 'image_shape': list(frame.shape),
271
- 'source_url': image_url
272
- }
273
-
274
- return JSONResponse(content=response_data)
275
-
276
- except aiohttp.ClientError as e:
277
- raise HTTPException(
278
- status_code=400,
279
- detail=f"Failed to fetch image from URL: {str(e)}"
280
- )
281
  except Exception as e:
282
- raise HTTPException(
283
- status_code=500,
284
- detail=f"An error occurred while processing the image: {str(e)}"
285
  )
286
 
287
- @app.get("/templates")
288
- async def list_templates():
289
- """Returns a list of all loaded cursor template names."""
290
- return {"templates": list(CURSOR_TEMPLATES.keys()), "count": len(CURSOR_TEMPLATES)}
291
-
292
  port = int(os.environ.get("PORT", 7860))
293
 
294
- # Launch FastAPI with uvicorn when run directly
295
  if __name__ == "__main__":
296
- import uvicorn
297
- uvicorn.run(app, host="0.0.0.0", port=port, timeout_keep_alive=75)
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import sys
3
+ import time
4
+ import subprocess
5
+ import numpy as np
6
+ from PIL import Image
7
+ from io import BytesIO
8
+ import requests
9
+ import threading
10
+
11
+ # FastAPI imports
12
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form
13
  from fastapi.responses import JSONResponse
 
 
 
14
  import uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ # 1. Environment Setup & Dependency Installation
17
+ def setup_environment():
18
+ print("--- Setting up environment ---")
19
+ dependencies = ["huggingface_hub", "onnxruntime", "transformers", "pillow", "numpy"]
20
+ try:
21
+ import huggingface_hub
22
+ import onnxruntime
23
+ import transformers
24
+ print("Dependencies already satisfied.")
25
+ except ImportError:
26
+ print("Installing dependencies...")
27
+ subprocess.check_call([sys.executable, "-m", "pip", "install"] + dependencies)
28
+
29
+ # 2. Model Download
30
+ def download_model(repo_id="Heliosoph/florence-2-base-ft-quantized-onnx", local_dir="florence2_quantized"):
31
+ from huggingface_hub import snapshot_download
32
+ if not os.path.exists(local_dir):
33
+ print(f"--- Downloading model from {repo_id} ---")
34
+ snapshot_download(repo_id=repo_id, local_dir=local_dir)
35
+ print("Download complete.")
36
+ else:
37
+ print(f"Model directory '{local_dir}' already exists.")
 
38
 
39
+ # 3. Inference Engine
40
+ class Florence2ONNXEngine:
41
+ def __init__(self, model_dir="florence2_quantized"):
42
+ import onnxruntime as ort
43
+ from transformers import CLIPImageProcessor, AutoTokenizer
44
+
45
+ self.model_dir = model_dir
46
+ print("--- Initializing ONNX Engine ---")
47
 
48
+ # Load processors
49
+ self.image_processor = CLIPImageProcessor.from_pretrained("microsoft/Florence-2-base-ft")
50
+ self.tokenizer = AutoTokenizer.from_pretrained("facebook/bart-base")
51
+
52
+ # Load ONNX sessions
53
+ providers = ['CPUExecutionProvider']
54
+ self.vision_session = ort.InferenceSession(os.path.join(model_dir, 'vision_encoder_quantized.onnx'), providers=providers)
55
+ self.embed_session = ort.InferenceSession(os.path.join(model_dir, 'embed_tokens_quantized.onnx'), providers=providers)
56
+ self.encoder_session = ort.InferenceSession(os.path.join(model_dir, 'encoder_model_quantized.onnx'), providers=providers)
57
+ self.decoder_session = ort.InferenceSession(os.path.join(model_dir, 'decoder_model_quantized.onnx'), providers=providers)
58
+ print("✓ Florence-2 ONNX Engine initialized successfully")
59
+
60
+ def generate_caption(self, image_path=None, image_array=None, task_prompt="<MORE_DETAILED_CAPTION>", max_new_tokens=1024):
61
+ """Generate caption from image path or PIL Image object"""
62
+ if image_path:
63
+ image = Image.open(image_path).convert("RGB")
64
+ elif image_array is not None and isinstance(image_array, Image.Image):
65
+ image = image_array.convert("RGB")
66
+ else:
67
+ raise ValueError("Either image_path or image_array must be provided")
68
 
69
+ print(f"--- Running Inference (Max Tokens: {max_new_tokens}) ---")
70
+ pixel_values = self.image_processor(images=image, return_tensors="np")['pixel_values']
71
+
72
+ # Map specific prompts to descriptive strings if needed
73
+ prompt_map = {
74
+ "<CAPTION>": "What does the image describe?",
75
+ "<DETAILED_CAPTION>": "Describe this image in detail.",
76
+ "<MORE_DETAILED_CAPTION>": "Describe this image in great detail with every object and background."
77
+ }
78
+ text_prompt = prompt_map.get(task_prompt, task_prompt)
79
+ input_ids = self.tokenizer(text_prompt, return_tensors="np")['input_ids']
80
 
81
+ # 1. Vision Features
82
+ start_time = time.time()
83
+ image_features = self.vision_session.run(None, {'pixel_values': pixel_values})[0]
 
 
 
84
 
85
+ # 2. Text Embeddings
86
+ text_embeds = self.embed_session.run(None, {'input_ids': input_ids})[0]
87
 
88
+ # 3. Encoder Fusion
89
+ combined_embeds = np.concatenate([image_features, text_embeds], axis=1)
90
+ attention_mask = np.ones((1, combined_embeds.shape[1]), dtype=np.int64)
91
+ encoder_outputs = self.encoder_session.run(None, {
92
+ 'inputs_embeds': combined_embeds,
93
+ 'attention_mask': attention_mask
94
+ })
95
+ last_hidden_state = encoder_outputs[0]
96
+
97
+ # 4. Autoregressive Decoding with Repetition Penalty
98
+ generated_ids = [2] # BART Start Token
99
+ min_new_tokens = 250 # Enforce minimum generation
100
+ repetition_penalty = 1.5 # Penalize repeated tokens
101
+
102
+ for i in range(max_new_tokens):
103
+ decoder_input_ids = np.array([generated_ids], dtype=np.int64)
104
+ decoder_embeds = self.embed_session.run(None, {'input_ids': decoder_input_ids})[0]
105
+
106
+ logits = self.decoder_session.run(None, {
107
+ 'inputs_embeds': decoder_embeds,
108
+ 'encoder_hidden_states': last_hidden_state,
109
+ 'encoder_attention_mask': attention_mask
110
+ })[0]
111
+
112
+ # Apply repetition penalty to recently generated tokens
113
+ current_logits = logits[0, -1, :].copy()
114
+ for prev_token in set(generated_ids[-50:]): # Check last 50 tokens
115
+ if current_logits[prev_token] > 0:
116
+ current_logits[prev_token] /= repetition_penalty
117
+ else:
118
+ current_logits[prev_token] *= repetition_penalty
119
+
120
+ next_token = np.argmax(current_logits)
121
+ # Only allow EOS token after minimum generation
122
+ if next_token == 2 and i < min_new_tokens:
123
+ # Force a different token by reducing EOS probability
124
+ current_logits[2] = -1e9
125
+ next_token = np.argmax(current_logits)
126
+ if next_token == 2: break # EOS Token
127
+ generated_ids.append(next_token)
128
+
129
+ if (i + 1) % 50 == 0:
130
+ print(f"Generated {i+1} tokens...")
131
+
132
+ end_time = time.time()
133
+ caption = self.tokenizer.decode(generated_ids, skip_special_tokens=True)
134
+
135
+ print(f"Inference complete in {end_time - start_time:.2f}s")
136
+ return caption
137
 
 
 
 
138
 
139
+ # Global engine instance
140
+ engine = None
 
 
 
 
 
 
 
 
141
 
142
+ def initialize_engine():
143
+ """Initialize the Florence2 ONNX engine"""
144
+ global engine
145
+ setup_environment()
146
+ download_model()
147
+ engine = Florence2ONNXEngine()
148
 
149
+ # FastAPI app setup
150
  app = FastAPI(
151
+ title="Florence-2 ONNX Image Captioning Server",
152
+ description="Auto-captions images using Florence-2 ONNX models"
153
  )
154
 
155
+ def load_image_from_url(image_url: str) -> Image.Image:
156
+ """Load an image from a URL."""
157
+ try:
158
+ response = requests.get(image_url, timeout=30)
159
+ response.raise_for_status()
160
+ image = Image.open(BytesIO(response.content))
161
+ return image.convert('RGB')
162
+ except Exception as e:
163
+ raise ValueError(f"Error loading image from URL: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
+ def load_image_from_bytes(image_bytes: bytes) -> Image.Image:
166
+ """Load an image from bytes."""
167
+ try:
168
+ image = Image.open(BytesIO(image_bytes))
169
+ return image.convert('RGB')
170
+ except Exception as e:
171
+ raise ValueError(f"Error loading image from bytes: {e}")
172
 
173
+ # API Endpoints
 
 
 
174
 
175
  @app.get("/")
176
  async def root():
177
+ """Root endpoint - shows server status"""
178
+ return {
179
+ "name": "Florence-2 ONNX Image Captioning Server",
180
+ "status": "running",
181
+ "model": "Florence-2-base-ft-quantized-onnx",
182
+ "model_loaded": engine is not None,
183
+ "endpoints": {
184
+ "GET /health": "Health check",
185
+ "GET /analyze": "Analyze image from URL",
186
+ "POST /analyze": "Analyze uploaded image",
187
+ }
188
+ }
189
+
190
+ @app.get("/health")
191
+ async def health():
192
+ """Health check endpoint"""
193
+ return {
194
+ "status": "healthy" if engine is not None else "initializing",
195
+ "model": "Florence-2-base-ft-quantized-onnx",
196
+ "model_loaded": engine is not None,
197
+ }
198
+
199
+ @app.get("/analyze")
200
+ async def analyze_get(image_url: str = None):
201
+ """Analyze an image by URL.
202
 
203
+ Usage: /analyze?image_url=https://example.com/image.jpg
204
+ """
205
+ try:
206
+ if engine is None:
207
+ raise HTTPException(status_code=503, detail="Model not initialized")
208
+
209
+ if not image_url:
210
+ raise HTTPException(status_code=400, detail="image_url query parameter is required")
211
+
212
+ # Load image from URL
213
+ image = load_image_from_url(image_url)
214
+
215
+ # Generate caption
216
+ caption = engine.generate_caption(image_array=image)
217
+
218
+ return JSONResponse(content={
219
+ "success": True,
220
+ "caption": caption,
221
+ "image_size": {"width": image.width, "height": image.height},
222
+ "model": "Florence-2-base-ft-quantized-onnx"
223
+ })
224
 
225
+ except HTTPException:
226
+ raise
227
+ except Exception as e:
228
+ return JSONResponse(
229
+ status_code=500,
230
+ content={"success": False, "error": str(e)}
231
  )
232
 
233
+ @app.post("/analyze")
234
+ async def analyze_post(file: UploadFile = File(None)):
235
+ """Analyze an uploaded image (multipart/form-data).
236
+
237
+ Returns: JSON with caption and metadata
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  """
 
 
 
 
 
 
239
  try:
240
+ if engine is None:
241
+ raise HTTPException(status_code=503, detail="Model not initialized")
 
 
 
 
 
 
 
 
242
 
243
+ if file is None:
244
+ raise HTTPException(status_code=400, detail="file is required")
 
245
 
246
+ # Read uploaded file
247
+ content = await file.read()
248
+
249
+ # Load image from bytes
250
+ try:
251
+ image = load_image_from_bytes(content)
252
+ except Exception as e:
253
+ raise HTTPException(status_code=400, detail=f"Failed to read uploaded image: {e}")
254
+
255
+ # Generate caption
256
+ caption = engine.generate_caption(image_array=image)
257
+
258
+ return JSONResponse(content={
259
+ "success": True,
260
+ "caption": caption,
261
+ "filename": file.filename,
262
+ "image_size": {"width": image.width, "height": image.height},
263
+ "model": "Florence-2-base-ft-quantized-onnx"
264
+ })
265
+
266
+ except HTTPException:
267
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  except Exception as e:
269
+ return JSONResponse(
270
+ status_code=500,
271
+ content={"success": False, "error": str(e)}
272
  )
273
 
274
+ # Get the port from environment variable
 
 
 
 
275
  port = int(os.environ.get("PORT", 7860))
276
 
277
+ # Launch server
278
  if __name__ == "__main__":
279
+ print("Initializing Florence-2 ONNX Engine...")
280
+ initialize_engine()
281
+
282
+ print(f"\n✓ Server ready! Starting on 0.0.0.0:{port}")
283
+ print(f"API Documentation: http://localhost:{port}/docs")
284
+
285
+ uvicorn.run(app, host="0.0.0.0", port=port)
cursors/1.png DELETED
Binary file (7.23 kB)
 
cursors/10.png DELETED
Binary file (3.3 kB)
 
cursors/11.png DELETED
Binary file (5.58 kB)
 
cursors/12.png DELETED
Binary file (3.77 kB)
 
cursors/13.png DELETED
Binary file (4.78 kB)
 
cursors/14.png DELETED
Binary file (3.92 kB)
 
cursors/15.png DELETED
Binary file (12.1 kB)
 
cursors/16.png DELETED
Binary file (15.5 kB)
 
cursors/2.png DELETED
Binary file (5.66 kB)
 
cursors/3.png DELETED
Binary file (2.75 kB)
 
cursors/4.png DELETED
Binary file (4.78 kB)
 
cursors/5.png DELETED
Binary file (5.45 kB)
 
cursors/6.png DELETED
Binary file (6.08 kB)
 
cursors/7.png DELETED
Binary file (5.72 kB)
 
cursors/8.png DELETED
Binary file (4.94 kB)
 
cursors/9.png DELETED
Binary file (3.47 kB)
 
requirements.txt CHANGED
@@ -1,11 +1,9 @@
1
- fastapi==0.104.1
2
- uvicorn==0.24.0
3
- aiofiles==23.2.1
4
- python-multipart==0.0.6
5
- huggingface-hub==0.18.0
6
- aiohttp
7
- jinja2
8
- pydantic
9
- datasets
10
- opencv-python
11
- numpy
 
1
+ numpy
2
+ pillow
3
+ huggingface_hub
4
+ onnxruntime
5
+ transformers
6
+ fastapi
7
+ uvicorn[standard]
8
+ requests
9
+ python-multipart