videopix commited on
Commit
735c9b7
Β·
verified Β·
1 Parent(s): fc19af7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -83
app.py CHANGED
@@ -1,11 +1,14 @@
1
  import io
2
  import asyncio
3
- import os
4
- from fastapi import FastAPI, File, UploadFile, Header, HTTPException
 
5
  from fastapi.responses import JSONResponse, HTMLResponse
6
  from PIL import Image
7
  import torch
8
  from transformers import AutoProcessor, AutoModelForCausalLM
 
 
9
 
10
  # ---------------------------------------------------
11
  # FastAPI App
@@ -14,30 +17,16 @@ app = FastAPI(title="Florence Image Caption API")
14
 
15
  device = "cuda" if torch.cuda.is_available() else "cpu"
16
 
 
17
  processor = None
18
  model = None
19
  model_lock = asyncio.Lock()
20
 
21
- # -------- TOKEN from HF Space Secrets ----------
22
- API_TOKEN = os.getenv("img2caption") # your secret token
23
-
24
-
25
- # ---------------------------------------------------
26
- # Verify Token for API only
27
- # ---------------------------------------------------
28
- def verify_token(auth: str | None):
29
- if auth is None or not auth.startswith("Bearer "):
30
- raise HTTPException(status_code=401, detail="Missing Authorization header")
31
-
32
- token = auth.split("Bearer ")[-1].strip()
33
- if token != API_TOKEN:
34
- raise HTTPException(status_code=403, detail="Invalid token")
35
-
36
 
37
- # ---------------------------------------------------
38
- # Lazy Load Model
39
- # ---------------------------------------------------
40
  async def load_model():
 
41
  global processor, model
42
 
43
  if model is None:
@@ -52,6 +41,7 @@ async def load_model():
52
 
53
 
54
  def run_caption(image: Image.Image) -> str:
 
55
  inputs = processor(
56
  text="<MORE_DETAILED_CAPTION>",
57
  images=image,
@@ -77,39 +67,46 @@ def run_caption(image: Image.Image) -> str:
77
 
78
 
79
  # ---------------------------------------------------
80
- # PUBLIC LOGIN PAGE
81
  # ---------------------------------------------------
82
- @app.get("/", response_class=HTMLResponse)
83
- def login_page():
84
- return """
85
- <!DOCTYPE html>
86
- <html>
87
- <head><title>Login</title></head>
88
- <body style="font-family:Arial;max-width:500px;margin:40px auto;">
 
 
 
 
 
 
 
89
 
90
- <h2>Enter Access Token</h2>
91
- <input id="token" type="password" style="width:100%;padding:10px;" placeholder="Enter token">
92
- <button onclick="login()" style="padding:10px;margin-top:10px;width:100%;">Continue</button>
 
93
 
94
- <script>
95
- function login() {
96
- const t = document.getElementById("token").value;
97
- if (!t) return alert("Token required");
98
- sessionStorage.setItem("authToken", t);
99
- window.location.href = "/ui";
100
- }
101
- </script>
102
 
103
- </body>
104
- </html>
105
- """
 
 
 
 
106
 
107
 
108
  # ---------------------------------------------------
109
- # PUBLIC UI PAGE (no token required)
110
  # ---------------------------------------------------
111
- @app.get("/ui", response_class=HTMLResponse)
112
- def ui_page():
113
  return """
114
  <!DOCTYPE html>
115
  <html>
@@ -118,11 +115,19 @@ def ui_page():
118
  <style>
119
  body { font-family: Arial; max-width: 650px; margin: 40px auto; }
120
  h2 { text-align: center; }
121
- #preview { width: 100%; margin-top: 15px; display: none; border-radius: 8px; }
122
- #captionBox { margin-top: 20px; padding: 15px; background: #eee; border-radius: 6px; display: none; }
123
- button { padding: 12px; width: 100%; margin-top: 10px;
124
- background: #4A90E2; color: white; border: none;
125
- border-radius: 6px; cursor: pointer; font-size: 16px; }
 
 
 
 
 
 
 
 
126
  button:hover { background: #357ABD; }
127
  </style>
128
  </head>
@@ -138,12 +143,6 @@ def ui_page():
138
  <div id="captionBox"></div>
139
 
140
  <script>
141
- let token = sessionStorage.getItem("authToken");
142
- if (!token) {
143
- alert("No token found, please login again.");
144
- window.location.href = "/";
145
- }
146
-
147
  const imageInput = document.getElementById("imageInput");
148
  const preview = document.getElementById("preview");
149
  const captionBox = document.getElementById("captionBox");
@@ -167,16 +166,15 @@ def ui_page():
167
  form.append("file", f);
168
 
169
  captionBox.style.display = "block";
170
- captionBox.textContent = "Generating caption...";
171
 
172
  const res = await fetch("/img2caption", {
173
  method: "POST",
174
- headers: { "Authorization": "Bearer " + token },
175
  body: form
176
  });
177
 
178
  const data = await res.json();
179
- captionBox.textContent = data.caption || data.error;
180
  }
181
  </script>
182
 
@@ -185,33 +183,10 @@ def ui_page():
185
  """
186
 
187
 
188
- # ---------------------------------------------------
189
- # PROTECTED API ENDPOINT
190
- # ---------------------------------------------------
191
- @app.post("/img2caption")
192
- async def img2caption(file: UploadFile = File(...), authorization: str = Header(None)):
193
- verify_token(authorization)
194
-
195
- try:
196
- async with model_lock:
197
- await load_model()
198
-
199
- data = await file.read()
200
- image = Image.open(io.BytesIO(data)).convert("RGB")
201
- caption = run_caption(image)
202
-
203
- return {"caption": caption}
204
-
205
- except Exception as e:
206
- return JSONResponse({"error": str(e)}, status_code=500)
207
-
208
-
209
- # ---------------------------------------------------
210
- # Local Run
211
- # ---------------------------------------------------
212
  def keep_alive():
213
  pass
214
 
 
215
  if __name__ == "__main__":
216
  import uvicorn
217
  print("πŸš€ Launching Fast img2caption API")
 
1
  import io
2
  import asyncio
3
+ import threading
4
+ import time
5
+ from fastapi import FastAPI, File, UploadFile, Header
6
  from fastapi.responses import JSONResponse, HTMLResponse
7
  from PIL import Image
8
  import torch
9
  from transformers import AutoProcessor, AutoModelForCausalLM
10
+ import requests
11
+ import os
12
 
13
  # ---------------------------------------------------
14
  # FastAPI App
 
17
 
18
  device = "cuda" if torch.cuda.is_available() else "cpu"
19
 
20
+ # Lazy load model on first request (prevents HF timeout)
21
  processor = None
22
  model = None
23
  model_lock = asyncio.Lock()
24
 
25
+ # Hugging Face token stored in HF Secrets
26
+ HF_TOKEN = os.getenv("img2caption")
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
 
 
 
28
  async def load_model():
29
+ """Load Florence model only when first needed."""
30
  global processor, model
31
 
32
  if model is None:
 
41
 
42
 
43
  def run_caption(image: Image.Image) -> str:
44
+ """Perform caption generation."""
45
  inputs = processor(
46
  text="<MORE_DETAILED_CAPTION>",
47
  images=image,
 
67
 
68
 
69
  # ---------------------------------------------------
70
+ # API Endpoint (Token enforced only when app sends a token)
71
  # ---------------------------------------------------
72
+ @app.post("/img2caption")
73
+ async def img2caption(
74
+ file: UploadFile = File(...),
75
+ authorization: str = Header(None)
76
+ ):
77
+ # Apps must send token β†’ enforce check
78
+ # UI sends no token β†’ skip check β†’ allow
79
+ if authorization is not None:
80
+ if not authorization.startswith("Bearer "):
81
+ return JSONResponse({"error": "Invalid token format"}, status_code=403)
82
+
83
+ token = authorization.replace("Bearer ", "").strip()
84
+ if token != HF_TOKEN:
85
+ return JSONResponse({"error": "Invalid token"}, status_code=403)
86
 
87
+ try:
88
+ # Ensure model is loaded
89
+ async with model_lock:
90
+ await load_model()
91
 
92
+ # Read and convert image
93
+ data = await file.read()
94
+ image = Image.open(io.BytesIO(data)).convert("RGB")
 
 
 
 
 
95
 
96
+ # Caption
97
+ caption = run_caption(image)
98
+
99
+ return {"caption": caption}
100
+
101
+ except Exception as e:
102
+ return JSONResponse({"error": str(e)}, status_code=500)
103
 
104
 
105
  # ---------------------------------------------------
106
+ # Simple HTML UI (NO token required)
107
  # ---------------------------------------------------
108
+ @app.get("/", response_class=HTMLResponse)
109
+ def ui():
110
  return """
111
  <!DOCTYPE html>
112
  <html>
 
115
  <style>
116
  body { font-family: Arial; max-width: 650px; margin: 40px auto; }
117
  h2 { text-align: center; }
118
+ #preview {
119
+ width: 100%; margin-top: 15px; display: none;
120
+ border-radius: 8px;
121
+ }
122
+ #captionBox {
123
+ margin-top: 20px; padding: 15px;
124
+ background: #eee; border-radius: 6px; display: none;
125
+ }
126
+ button {
127
+ padding: 12px; width: 100%; margin-top: 10px;
128
+ background: #4A90E2; color: white; border: none;
129
+ border-radius: 6px; cursor: pointer; font-size: 16px;
130
+ }
131
  button:hover { background: #357ABD; }
132
  </style>
133
  </head>
 
143
  <div id="captionBox"></div>
144
 
145
  <script>
 
 
 
 
 
 
146
  const imageInput = document.getElementById("imageInput");
147
  const preview = document.getElementById("preview");
148
  const captionBox = document.getElementById("captionBox");
 
166
  form.append("file", f);
167
 
168
  captionBox.style.display = "block";
169
+ captionBox.innerHTML = "Generating caption...";
170
 
171
  const res = await fetch("/img2caption", {
172
  method: "POST",
 
173
  body: form
174
  });
175
 
176
  const data = await res.json();
177
+ captionBox.innerHTML = data.caption || data.error;
178
  }
179
  </script>
180
 
 
183
  """
184
 
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  def keep_alive():
187
  pass
188
 
189
+
190
  if __name__ == "__main__":
191
  import uvicorn
192
  print("πŸš€ Launching Fast img2caption API")