sairamv1 commited on
Commit
aaee5da
Β·
verified Β·
1 Parent(s): e01ec82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -35
app.py CHANGED
@@ -4,13 +4,14 @@ from contextlib import asynccontextmanager
4
  # ─── 1. MANDATORY BACKEND CONFIG (Must be at the very top) ───────────────────
5
  os.environ["KERAS_BACKEND"] = "jax"
6
  os.environ["JAX_PLATFORM_NAME"] = "cpu"
7
- os.environ["HF_HOME"] = "/tmp/huggingface" # HF Spaces writable directory
8
 
9
  import base64
10
  import logging
11
  import numpy as np
12
  import cv2
13
  import requests
 
14
  from typing import List, Dict, Any, Optional
15
 
16
  import keras
@@ -18,19 +19,22 @@ from fastapi import FastAPI, File, UploadFile, HTTPException
18
  from fastapi.responses import JSONResponse
19
  from huggingface_hub import hf_hub_download
20
  from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
 
21
 
22
  # ─── 2. LOGGING & CONFIG ─────────────────────────────────────────────────────
23
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
24
  logger = logging.getLogger(__name__)
25
 
26
  API_KEY = os.getenv("ROBOFLOW_API_KEY")
27
- WORKFLOW_URL = "https://serverless.roboflow.com/my-workspace-iy9ll/find-teeth"
 
28
  IMG_SIZE = 256
29
  THRESHOLD = 0.05
30
  MODEL_REPO = "sairamv1/TeethCariesDetection_CNNAutoencoder"
31
  MODEL_FILENAME = "autoencoder.keras"
32
 
33
  MODEL = None
 
34
 
35
  # ─── 3. RETRY-ENABLED MODEL DOWNLOAD ─────────────────────────────────────────
36
  @retry(
@@ -49,6 +53,22 @@ def download_model() -> str:
49
  logger.info(f"βœ… Model downloaded to {local_path}")
50
  return local_path
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  # ─── 4. LIFESPAN EVENT HANDLER ───────────────────────────────────────────────
53
  @asynccontextmanager
54
  async def lifespan(app: FastAPI):
@@ -59,13 +79,14 @@ async def lifespan(app: FastAPI):
59
  logger.info("πŸ”„ Loading Keras model with JAX backend...")
60
  MODEL = keras.saving.load_model(local_model_path)
61
  logger.info(f"βœ… Model loaded successfully! Input shape: {MODEL.input_shape}")
 
 
 
62
  except Exception as e:
63
- logger.error(f"❌ Model load failed: {e}")
64
  MODEL = None
65
 
66
- yield # Application runs here
67
-
68
- # Cleanup (if needed)
69
  logger.info("πŸ›‘ Shutting down...")
70
 
71
  app = FastAPI(
@@ -75,22 +96,19 @@ app = FastAPI(
75
  lifespan=lifespan
76
  )
77
 
78
- # ─── 5. ROBOFLOW API WITH RETRY ──────────────────────────────────────────────
79
- @retry(
80
- stop=stop_after_attempt(2),
81
- wait=wait_exponential(multiplier=1, min=1, max=5),
82
- retry=retry_if_exception_type(requests.RequestException)
83
- )
84
- def call_roboflow_api(image_base64: str) -> Dict:
85
- """Call Roboflow workflow with retry logic."""
86
- response = requests.post(
87
- WORKFLOW_URL,
88
- params={"api_key": API_KEY},
89
- json={"inputs": {"image": {"type": "base64", "value": image_base64}}},
90
- timeout=30
91
  )
92
- response.raise_for_status()
93
- return response.json()
94
 
95
  # ─── 6. ROUTES ───────────────────────────────────────────────────────────────
96
  @app.get("/")
@@ -100,7 +118,8 @@ async def root():
100
  "status": "active",
101
  "model_ready": MODEL is not None,
102
  "backend": os.environ.get("KERAS_BACKEND"),
103
- "api_configured": API_KEY is not None
 
104
  }
105
 
106
  @app.get("/health")
@@ -111,7 +130,11 @@ async def health():
111
  status_code=503,
112
  content={"status": "unhealthy", "reason": "Model not loaded"}
113
  )
114
- return {"status": "healthy", "model_input_shape": str(MODEL.input_shape)}
 
 
 
 
115
 
116
  @app.post("/analyze")
117
  async def analyze(file: UploadFile = File(...)):
@@ -123,7 +146,7 @@ async def analyze(file: UploadFile = File(...)):
123
  if MODEL is None:
124
  raise HTTPException(status_code=503, detail="Model not initialized. Please retry later.")
125
 
126
- if not API_KEY:
127
  raise HTTPException(status_code=500, detail="ROBOFLOW_API_KEY not configured")
128
 
129
  # ─── Read and validate image ─────────────────────────────────────────────
@@ -149,30 +172,36 @@ async def analyze(file: UploadFile = File(...)):
149
  raise HTTPException(status_code=400, detail=f"Image processing failed: {str(e)}")
150
 
151
  # ─── Call Roboflow detection ─────────────────────────────────────────────
 
152
  try:
153
- image_base64 = base64.b64encode(contents).decode("utf-8")
154
- result_json = call_roboflow_api(image_base64)
155
- except requests.Timeout:
156
- raise HTTPException(status_code=504, detail="Detection service timeout")
157
- except requests.RequestException as e:
158
- logger.error(f"Roboflow API error: {e}")
159
- raise HTTPException(status_code=502, detail="Detection service unavailable")
160
  except Exception as e:
161
- logger.error(f"Unexpected error: {e}")
162
- raise HTTPException(status_code=500, detail="Internal processing error")
 
 
 
 
163
 
164
  # ─── Parse detections ────────────────────────────────────────────────────
165
  try:
166
  if isinstance(result_json, list):
167
  output_data = result_json[0].get("outputs", {})
168
  else:
169
- output_data = result_json.get("outputs", [{}])[0]
170
 
171
  predictions = output_data.get("predictions", [])
172
  if isinstance(predictions, dict):
173
  predictions = predictions.get("predictions", [])
174
  except Exception as e:
175
  logger.warning(f"Response parsing issue: {e}")
 
176
  predictions = []
177
 
178
  if not predictions:
@@ -187,7 +216,7 @@ async def analyze(file: UploadFile = File(...)):
187
  batch_images = []
188
  metadata = []
189
 
190
- for i, det in enumerate(predictions[:12]): # Limit for performance
191
  try:
192
  xc, yc, w, h = det["x"], det["y"], det["width"], det["height"]
193
  x1 = max(0, int(xc - w/2))
 
4
  # ─── 1. MANDATORY BACKEND CONFIG (Must be at the very top) ───────────────────
5
  os.environ["KERAS_BACKEND"] = "jax"
6
  os.environ["JAX_PLATFORM_NAME"] = "cpu"
7
+ os.environ["HF_HOME"] = "/tmp/huggingface"
8
 
9
  import base64
10
  import logging
11
  import numpy as np
12
  import cv2
13
  import requests
14
+ import tempfile
15
  from typing import List, Dict, Any, Optional
16
 
17
  import keras
 
19
  from fastapi.responses import JSONResponse
20
  from huggingface_hub import hf_hub_download
21
  from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
22
+ from inference_sdk import InferenceHTTPClient
23
 
24
  # ─── 2. LOGGING & CONFIG ─────────────────────────────────────────────────────
25
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
  logger = logging.getLogger(__name__)
27
 
28
  API_KEY = os.getenv("ROBOFLOW_API_KEY")
29
+ WORKSPACE_NAME = "my-workspace-iy9ll"
30
+ WORKFLOW_ID = "find-teeth"
31
  IMG_SIZE = 256
32
  THRESHOLD = 0.05
33
  MODEL_REPO = "sairamv1/TeethCariesDetection_CNNAutoencoder"
34
  MODEL_FILENAME = "autoencoder.keras"
35
 
36
  MODEL = None
37
+ roboflow_client = None
38
 
39
  # ─── 3. RETRY-ENABLED MODEL DOWNLOAD ─────────────────────────────────────────
40
  @retry(
 
53
  logger.info(f"βœ… Model downloaded to {local_path}")
54
  return local_path
55
 
56
+ def init_roboflow_client():
57
+ """Initialize Roboflow client if API key is available."""
58
+ global roboflow_client
59
+ if API_KEY:
60
+ try:
61
+ roboflow_client = InferenceHTTPClient(
62
+ api_url="https://serverless.roboflow.com",
63
+ api_key=API_KEY
64
+ )
65
+ logger.info("βœ… Roboflow client initialized")
66
+ except Exception as e:
67
+ logger.error(f"❌ Roboflow client initialization failed: {e}")
68
+ roboflow_client = None
69
+ else:
70
+ logger.warning("⚠️ ROBOFLOW_API_KEY not set")
71
+
72
  # ─── 4. LIFESPAN EVENT HANDLER ───────────────────────────────────────────────
73
  @asynccontextmanager
74
  async def lifespan(app: FastAPI):
 
79
  logger.info("πŸ”„ Loading Keras model with JAX backend...")
80
  MODEL = keras.saving.load_model(local_model_path)
81
  logger.info(f"βœ… Model loaded successfully! Input shape: {MODEL.input_shape}")
82
+
83
+ # Initialize Roboflow client
84
+ init_roboflow_client()
85
  except Exception as e:
86
+ logger.error(f"❌ Startup failed: {e}")
87
  MODEL = None
88
 
89
+ yield
 
 
90
  logger.info("πŸ›‘ Shutting down...")
91
 
92
  app = FastAPI(
 
96
  lifespan=lifespan
97
  )
98
 
99
+ # ─── 5. ROBOFLOW API CALL ────────────────────────────────────────────────────
100
+ def call_roboflow_api(image_path: str) -> Dict:
101
+ """Call Roboflow workflow using inference SDK."""
102
+ if not roboflow_client:
103
+ raise Exception("Roboflow client not initialized")
104
+
105
+ result = roboflow_client.run_workflow(
106
+ workspace_name=WORKSPACE_NAME,
107
+ workflow_id=WORKFLOW_ID,
108
+ images={"image": image_path},
109
+ use_cache=False
 
 
110
  )
111
+ return result
 
112
 
113
  # ─── 6. ROUTES ───────────────────────────────────────────────────────────────
114
  @app.get("/")
 
118
  "status": "active",
119
  "model_ready": MODEL is not None,
120
  "backend": os.environ.get("KERAS_BACKEND"),
121
+ "api_configured": API_KEY is not None,
122
+ "roboflow_ready": roboflow_client is not None
123
  }
124
 
125
  @app.get("/health")
 
130
  status_code=503,
131
  content={"status": "unhealthy", "reason": "Model not loaded"}
132
  )
133
+ return {
134
+ "status": "healthy",
135
+ "model_input_shape": str(MODEL.input_shape),
136
+ "roboflow_status": "ready" if roboflow_client else "not configured"
137
+ }
138
 
139
  @app.post("/analyze")
140
  async def analyze(file: UploadFile = File(...)):
 
146
  if MODEL is None:
147
  raise HTTPException(status_code=503, detail="Model not initialized. Please retry later.")
148
 
149
+ if not roboflow_client:
150
  raise HTTPException(status_code=500, detail="ROBOFLOW_API_KEY not configured")
151
 
152
  # ─── Read and validate image ─────────────────────────────────────────────
 
172
  raise HTTPException(status_code=400, detail=f"Image processing failed: {str(e)}")
173
 
174
  # ─── Call Roboflow detection ─────────────────────────────────────────────
175
+ tmp_path = None
176
  try:
177
+ # Save to temp file for inference_sdk
178
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
179
+ tmp.write(contents)
180
+ tmp_path = tmp.name
181
+
182
+ result_json = call_roboflow_api(tmp_path)
183
+
184
  except Exception as e:
185
+ logger.error(f"Roboflow workflow error: {e}")
186
+ raise HTTPException(status_code=502, detail=f"Detection service failed: {str(e)}")
187
+ finally:
188
+ # Clean up temp file
189
+ if tmp_path and os.path.exists(tmp_path):
190
+ os.remove(tmp_path)
191
 
192
  # ─── Parse detections ────────────────────────────────────────────────────
193
  try:
194
  if isinstance(result_json, list):
195
  output_data = result_json[0].get("outputs", {})
196
  else:
197
+ output_data = result_json.get("outputs", [{}])[0] if isinstance(result_json.get("outputs"), list) else result_json.get("outputs", {})
198
 
199
  predictions = output_data.get("predictions", [])
200
  if isinstance(predictions, dict):
201
  predictions = predictions.get("predictions", [])
202
  except Exception as e:
203
  logger.warning(f"Response parsing issue: {e}")
204
+ logger.warning(f"Raw response: {result_json}")
205
  predictions = []
206
 
207
  if not predictions:
 
216
  batch_images = []
217
  metadata = []
218
 
219
+ for i, det in enumerate(predictions[:12]):
220
  try:
221
  xc, yc, w, h = det["x"], det["y"], det["width"], det["height"]
222
  x1 = max(0, int(xc - w/2))