sairamv1 commited on
Commit
9cc108e
Β·
verified Β·
1 Parent(s): c4f78f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -4
app.py CHANGED
@@ -1,7 +1,125 @@
1
- from fastapi import FastAPI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  app = FastAPI()
4
 
5
- @app.get("/")
6
- def read_root():
7
- return {"status": "Backend is running and waiting for model details!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ from typing import List, Dict, Any
4
+
5
+ import numpy as np
6
+ import cv2
7
+ import requests
8
+ from fastapi import FastAPI, File, UploadFile, HTTPException
9
+
10
+ os.environ["KERAS_BACKEND"] = "jax"
11
+ import keras
12
+
13
+ # ─── Config ───────────────────────────────────────────────────────────────────
14
+ API_KEY = os.environ["ROBOFLOW_API_KEY"]
15
+ WORKFLOW_URL = "https://serverless.roboflow.com/YOUR_WORKSPACE/YOUR_WORKFLOW" # adjust
16
+ IMG_SIZE = 256
17
+ THRESHOLD = 0.05 # ideally derived from healthy images [file:1]
18
+
19
+ # ─── Load model once at startup ───────────────────────────────────────────────
20
+ MODEL = keras.saving.load_model("hf://sairamv1/TeethCariesDetection_CNNAutoencoder")
21
 
22
  app = FastAPI()
23
 
24
+
25
+ def first(seq):
26
+ """Return first element or None."""
27
+ return seq[0] if seq else None
28
+
29
+
30
+ @app.post("/analyze")
31
+ async def analyze(file: UploadFile = File(...)):
32
+ # ── Read & decode image ───────────────────────────────────────────────────
33
+ contents = await file.read()
34
+ nparr = np.frombuffer(contents, np.uint8)
35
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
36
+ if img is None:
37
+ raise HTTPException(status_code=400, detail="Invalid image file")
38
+
39
+ img_h, img_w = img.shape[:2]
40
+
41
+ # ── 1. Call Roboflow ──────────────────────────────────────────────────────
42
+ image_base64 = base64.b64encode(contents).decode("utf-8")
43
+ try:
44
+ response = requests.post(
45
+ WORKFLOW_URL,
46
+ json={
47
+ "api_key": API_KEY,
48
+ "inputs": {"image": {"type": "base64", "value": image_base64}},
49
+ },
50
+ timeout=60,
51
+ )
52
+ response.raise_for_status()
53
+ except requests.exceptions.Timeout:
54
+ raise HTTPException(status_code=504, detail="Roboflow request timed out")
55
+ except requests.exceptions.RequestException as e:
56
+ raise HTTPException(status_code=502, detail=f"Roboflow error: {e}")
57
+
58
+ result = response.json()
59
+ item0 = first(result)
60
+ if not item0:
61
+ return {"tooth_count": 0, "results": []}
62
+
63
+ outputs = first(item0.get("outputs", []))
64
+ pred_blob = outputs.get("predictions", {}) if outputs else {}
65
+ predictions_list: List[Dict[str, Any]] = (
66
+ pred_blob.get("predictions", []) if isinstance(pred_blob, dict) else (pred_blob or [])
67
+ )
68
+
69
+ if not predictions_list:
70
+ return {"tooth_count": 0, "results": []}
71
+
72
+ # ── 2. Prepare batch ──────────────────────────────────────────────────────
73
+ batch_images = []
74
+ metadata = []
75
+
76
+ for i, det in enumerate(predictions_list):
77
+ xc, yc, w, h = det["x"], det["y"], det["width"], det["height"]
78
+ x1 = max(0, int(round(xc - w / 2)))
79
+ y1 = max(0, int(round(yc - h / 2)))
80
+ x2 = min(img_w, int(round(xc + w / 2)))
81
+ y2 = min(img_h, int(round(yc + h / 2)))
82
+
83
+ crop = img[y1:y2, x1:x2]
84
+ if crop.size == 0:
85
+ continue
86
+
87
+ # Grayscale + CLAHE
88
+ gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
89
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
90
+ gray = clahe.apply(gray)
91
+
92
+ # Resize with padding (preserve aspect ratio)
93
+ ch, cw = gray.shape[:2]
94
+ scale = IMG_SIZE / max(ch, cw)
95
+ new_w, new_h = max(1, int(round(cw * scale))), max(1, int(round(ch * scale)))
96
+ resized = cv2.resize(gray, (new_w, new_h), interpolation=cv2.INTER_AREA)
97
+
98
+ canvas = np.zeros((IMG_SIZE, IMG_SIZE), dtype=np.uint8)
99
+ y0, x0 = (IMG_SIZE - new_h) // 2, (IMG_SIZE - new_w) // 2
100
+ canvas[y0 : y0 + new_h, x0 : x0 + new_w] = resized
101
+
102
+ final_img = (canvas.astype(np.float32) / 255.0)[..., np.newaxis]
103
+ batch_images.append(final_img)
104
+ metadata.append({"id": i, "box": [x1, y1, x2, y2]})
105
+
106
+ if not batch_images:
107
+ return {"tooth_count": 0, "results": []}
108
+
109
+ # ── 3. Batch inference ────────────────────────────────────────────────────
110
+ batch_array = np.array(batch_images) # (N, 256, 256, 1)
111
+ reconstructions = MODEL.predict(batch_array)
112
+
113
+ # ── 4. Anomaly scores ────────────────────────────────────────────────────���
114
+ errors = np.mean(np.abs(batch_array - reconstructions), axis=(1, 2, 3))
115
+
116
+ final_results = []
117
+ for idx, score in enumerate(errors):
118
+ final_results.append({
119
+ "tooth_index": metadata[idx]["id"],
120
+ "score": float(score),
121
+ "is_anomaly": bool(score > THRESHOLD),
122
+ "bounding_box": metadata[idx]["box"],
123
+ })
124
+
125
+ return {"tooth_count": len(final_results), "results": final_results}