mahmoudelsheemy commited on
Commit
3a1ed33
·
1 Parent(s): 14d1289

Deploy FastAPI teeth detection API

Browse files
Files changed (6) hide show
  1. .gitignore +0 -0
  2. Dockerfile +0 -0
  3. README.md +0 -11
  4. app.py +1325 -0
  5. knowledge_base/clinical_rules.json +331 -0
  6. requirements.txt +0 -0
.gitignore ADDED
File without changes
Dockerfile ADDED
File without changes
README.md CHANGED
@@ -1,11 +0,0 @@
1
- ---
2
- title: Teeth Detection Api
3
- emoji: 👀
4
- colorFrom: red
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,1325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # from fastapi import FastAPI, File, Path
3
+ # from fastapi.middleware.cors import CORSMiddleware
4
+ # import uvicorn
5
+ # import numpy as np
6
+ # from io import BytesIO
7
+ # from PIL import Image
8
+ # import tensorflow as tf
9
+ # from tensorflow.keras.applications.efficientnet import preprocess_input # type: ignore
10
+ # import uuid
11
+ # from fastapi import File, UploadFile, Query, HTTPException
12
+ # from fastapi.responses import JSONResponse
13
+ # import time
14
+
15
+ # # HuggingFace
16
+ # from transformers import pipeline
17
+ # import torch
18
+ # import json
19
+ # import os
20
+ # from pathlib import Path
21
+ # from datetime import datetime
22
+
23
+ # BASE_DIR = Path(__file__).parent
24
+ # KNOWLEDGE_BASE_PATH = BASE_DIR / "knowledge_base" / "clinical_rules.json"
25
+
26
+
27
+
28
+ # # ============================================================
29
+ # # CONFIGURATION
30
+ # # ============================================================
31
+ # IMAGE_SIZE = 224
32
+
33
+ # # Local TensorFlow models
34
+ # BINARY_MODEL_PATH = r"D:\Colab-python\teethDises\new\api\models\model_2.keras"
35
+ # DISEASE_MODEL_PATH = r"D:\Colab-python\teethDises\new\api\models\LAST_model_efficent.h5"
36
+
37
+ # # BINARY_MODEL_PATH = "./model_2.keras"
38
+ # # DISEASE_MODEL_PATH = "./LAST_model_efficent.h5"
39
+
40
+ # import os
41
+ # BASE_DIR = os.path.dirname(os.path.abspath(__file__))
42
+ # MODELS_DIR = os.path.join(BASE_DIR, "models")
43
+ # BINARY_MODEL_PATH = os.path.join(MODELS_DIR, "model_2.keras")
44
+ # DISEASE_MODEL_PATH = os.path.join(MODELS_DIR, "LAST_model_efficent.h5")
45
+
46
+ # print(os.path.exists(BINARY_MODEL_PATH))
47
+ # print(os.path.isfile(BINARY_MODEL_PATH))
48
+
49
+
50
+ # # HuggingFace Teeth Health Model
51
+ # HF_TEETH_HEALTH_MODEL = "steven123/Check_GoodBad_Teeth"
52
+
53
+ # DEVICE = 0 if torch.cuda.is_available() else -1
54
+
55
+ # BINARY_CLASSES = ["not_teath", "teath"]
56
+ # TEETH_HEALTH_CLASSES = ["Good Teeth", "Bad Teeth"]
57
+
58
+ # DISEASE_CLASSES = [
59
+ # "Calculus",
60
+ # "Dental Caries",
61
+ # "Gingivitis",
62
+ # "Mouth Ulcer",
63
+ # "Tooth Discoloration",
64
+ # "Hypodontia"
65
+ # ]
66
+
67
+
68
+ # # ============================================================
69
+ # # LOAD KNOWLEDGE BASE
70
+ # # ============================================================
71
+ # def load_knowledge_base():
72
+ # """تحميل قاعدة المعرفة من ملف JSON"""
73
+ # try:
74
+ # with open(KNOWLEDGE_BASE_PATH, 'r', encoding='utf-8') as f:
75
+ # return json.load(f)
76
+ # except FileNotFoundError:
77
+ # print(f"⚠️ Warning: Knowledge base file not found at {KNOWLEDGE_BASE_PATH}")
78
+ # return {"diseases": {}, "general_rules": {}}
79
+
80
+ # knowledge_base_data = load_knowledge_base()
81
+ # diseases_db = knowledge_base_data.get("diseases", {})
82
+ # general_rules = knowledge_base_data.get("general_rules", {})
83
+
84
+
85
+
86
+ # # ============================================================
87
+ # # APPLICATION INITIALIZATION
88
+ # # ============================================================
89
+ # app = FastAPI(
90
+ # title="Integrated Teeth Detection System",
91
+ # description="Binary Detection → Teeth Health → Disease Classification",
92
+ # version="1.2.0"
93
+ # )
94
+
95
+ # app.add_middleware(
96
+ # CORSMiddleware,
97
+ # allow_origins=["*"],
98
+ # allow_credentials=True,
99
+ # allow_methods=["*"],
100
+ # allow_headers=["*"],
101
+ # )
102
+
103
+ # import os
104
+ # print("exists:", os.path.exists(BINARY_MODEL_PATH))
105
+ # print("isfile:", os.path.isfile(BINARY_MODEL_PATH))
106
+
107
+ # # ============================================================
108
+ # # # MODEL LOADING
109
+ # # ============================================================
110
+ # print("[INFO] Loading TensorFlow models...")
111
+ # BINARY_MODEL = tf.keras.models.load_model(BINARY_MODEL_PATH)
112
+ # DISEASE_MODEL = tf.keras.models.load_model(DISEASE_MODEL_PATH)
113
+
114
+ # print("[INFO] Loading HuggingFace Teeth Health model...")
115
+ # TEETH_HEALTH_MODEL = pipeline(
116
+ # "image-classification",
117
+ # model=HF_TEETH_HEALTH_MODEL,
118
+ # device=DEVICE
119
+ # )
120
+
121
+ # print("[INFO] All models loaded successfully")
122
+
123
+ # # ============================================================
124
+ # # IMAGE PREPROCESSING
125
+ # # ============================================================
126
+ # def load_image(image_bytes: bytes) -> Image.Image:
127
+ # return Image.open(BytesIO(image_bytes)).convert("RGB")
128
+
129
+ # def preprocess_for_binary(image_bytes: bytes) -> np.ndarray:
130
+ # image = load_image(image_bytes)
131
+ # image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
132
+ # image = np.array(image).astype(np.float32)
133
+ # return image
134
+
135
+ # def preprocess_for_disease(image_bytes: bytes) -> np.ndarray:
136
+ # image = load_image(image_bytes)
137
+ # image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
138
+ # image = np.array(image).astype(np.float32)
139
+ # image = preprocess_input(image)
140
+ # return image
141
+
142
+ # def assess_urgency(result):
143
+ # return {
144
+ # "level": result.get("urgency_level", "low"),
145
+ # "message": result.get("urgency_message", "")
146
+ # }
147
+ # def combine_advice(result):
148
+ # combined = []
149
+ # seen = set()
150
+
151
+ # for advice in result.get("personalized_home_care", []):
152
+ # if advice not in seen:
153
+ # combined.append(advice)
154
+ # seen.add(advice)
155
+
156
+ # return combined[:4]
157
+
158
+
159
+ # #===========================================================
160
+ # # RECOMMENDATION ENGINE
161
+ # # ============================================================
162
+ # def add_unique_advice(advice_list, target_list):
163
+ # """
164
+ # Adds advice to target_list if not already present (prevents exact duplicates)
165
+ # """
166
+ # if not advice_list:
167
+ # return
168
+ # for advice in advice_list:
169
+ # if advice and advice not in target_list:
170
+ # target_list.append(advice)
171
+ # #============================================================'
172
+ # # Advanced weighted recommendation
173
+ # #============================================================
174
+ # def get_weighted_recommendations(top_predictions, age: int, pain_level: int, bleeding: bool):
175
+ # result = {
176
+ # "timestamp": datetime.now().isoformat(),
177
+ # "primary_condition": None,
178
+ # "overall_risk_score": 0.0,
179
+ # "risk_category": "Early Stage",
180
+ # "clinical_overview": [],
181
+ # "priority_treatment_plan": [],
182
+ # "supportive_treatments": [],
183
+ # "personalized_home_care": {"essential": [], "recommended": [], "avoid": []},
184
+ # "follow_up_recommendation": [],
185
+ # "requires_dentist": False,
186
+ # "urgency_level": "low",
187
+ # "urgency_message": ""
188
+ # }
189
+
190
+ # if not top_predictions:
191
+ # return result
192
+
193
+ # severity_scale = {"high": 3, "medium": 2, "moderate":2, "mild": 1, "low": 1,"structural": 2}
194
+ # urgency_scale = {"high": 3, "medium": 2, "low": 1}
195
+ # confidence_rules = general_rules.get("confidence_weighting", {})
196
+
197
+ # filtered_predictions = [p for p in top_predictions if p.get("confidence", 0) > 0.05]
198
+ # if not filtered_predictions:
199
+ # return result
200
+
201
+ # total_conf = sum(p["confidence"] for p in filtered_predictions)
202
+ # total_risk_score = 0
203
+ # detected_conditions = []
204
+
205
+ # for pred in filtered_predictions:
206
+ # disease = pred["class"]
207
+ # confidence = pred["confidence"]
208
+ # weight = confidence / total_conf if total_conf > 0 else 0
209
+
210
+ # if disease not in diseases_db:
211
+ # continue
212
+
213
+ # detected_conditions.append(disease)
214
+ # disease_info = diseases_db[disease]
215
+ # base = disease_info.get("base_info", {})
216
+ # treatments = disease_info.get("treatment_options", {}).get("primary", [])
217
+ # home_advice = disease_info.get("home_advice", {})
218
+
219
+ # # 🔹 Severity & urgency
220
+ # severity = base.get("severity", "low")
221
+ # urgency = base.get("urgency", "low")
222
+ # severity_value = severity_scale.get(severity, 1)
223
+ # urgency_value = urgency_scale.get(urgency, 1)
224
+
225
+ # # --- Symptom-based adjustment (Improved Clinical Logic) ---
226
+ # bleeding_factor = 1 if bleeding else 0
227
+ # disease_category = base.get("category", "")
228
+
229
+ # # Option 1: Direct multiplier (pain_level 0-10)
230
+ # if disease_category in ["tooth_decay", "inflammatory"]:
231
+ # severity_value += pain_level * 0.3
232
+ # urgency_value += pain_level * 0.3
233
+ # elif disease_category in ["soft_tissue", "mineral_deposit","developmental","aesthetic"]:
234
+ # severity_value += pain_level * 0.1
235
+ # urgency_value += pain_level * 0.1
236
+
237
+ # # Bleeding impact
238
+ # if disease_category in ["inflammatory", "tooth_decay"]:
239
+ # urgency_value += bleeding_factor * 1.5
240
+ # elif disease_category in ["soft_tissue", "mineral_deposit","developmental","aesthetic"]:
241
+ # urgency_value += bleeding_factor * 0.4
242
+
243
+ # # Age sensitivity
244
+ # if age < 12 or age > 65:
245
+ # urgency_value += 0.5
246
+
247
+ # # حساب عامل الثقة بناءً على confidence
248
+ # if confidence >= 0.8:
249
+ # confidence_factor = 1.0
250
+ # elif confidence >= 0.5:
251
+ # confidence_factor = confidence_rules.get("medium", 0.5)
252
+ # else:
253
+ # confidence_factor = confidence_rules.get("low", 0.2)
254
+
255
+
256
+
257
+ # disease_risk = ((severity_value * 0.6 + urgency_value * 0.4) * weight * confidence_factor)
258
+ # total_risk_score += disease_risk
259
+
260
+ # # Treatment level
261
+ # if severity == "high":
262
+ # treatment_level = "aggressive"
263
+ # elif severity in ["medium", "structural"]:
264
+ # treatment_level = "moderate"
265
+ # else:
266
+ # treatment_level = "conservative"
267
+
268
+ # # Clinical overview
269
+ # result["clinical_overview"].append({
270
+ # "condition": disease,
271
+ # "confidence_percent": round(confidence * 100, 2),
272
+ # "impact_weight": round(weight, 3),
273
+ # "severity": severity,
274
+ # "urgency": urgency,
275
+ # "treatment_level": treatment_level
276
+ # })
277
+
278
+ # # Treatment plans
279
+ # if treatment_level == "aggressive":
280
+ # for t in treatments:
281
+ # add_unique_advice([t], result["priority_treatment_plan"])
282
+ # elif treatment_level == "moderate":
283
+ # for t in treatments[:1]:
284
+ # add_unique_advice([t], result["supportive_treatments"])
285
+
286
+ # # --- Home care advice (Compact & Professional) ---
287
+ # essential_advice = home_advice.get("essential", [])[:2]
288
+ # recommended_advice = home_advice.get("recommended", [])[:2]
289
+ # avoid_advice = home_advice.get("avoid", [])[:2]
290
+
291
+ # add_unique_advice(essential_advice, result["personalized_home_care"]["essential"])
292
+ # add_unique_advice(recommended_advice, result["personalized_home_care"]["recommended"])
293
+ # add_unique_advice(avoid_advice, result["personalized_home_care"]["avoid"])
294
+
295
+ # # Build-up advice
296
+ # build_up = disease_info.get("build_up_recommendation", {})
297
+ # if build_up.get("applicable", False):
298
+ # conditions = build_up.get("conditions", {})
299
+ # for cond in conditions.values():
300
+ # if confidence >= cond.get("confidence_threshold", 0.3):
301
+ # materials = ", ".join(cond.get("materials", []))
302
+ # reason = cond.get("reason", "")
303
+ # advice_text = f"Consider build-up using {materials} ({reason})"
304
+ # add_unique_advice([advice_text], result["personalized_home_care"]["recommended"])
305
+
306
+ # # Dentist requirement
307
+ # if base.get("requires_dentist", False):
308
+ # result["requires_dentist"] = True
309
+
310
+ # # Follow up
311
+ # follow_up = disease_info.get("follow_up")
312
+ # if follow_up:
313
+ # add_unique_advice([follow_up], result["follow_up_recommendation"])
314
+
315
+ # # Normalize risk
316
+ # normalized_risk = min(total_risk_score, 5)
317
+ # result["overall_risk_score"] = round(normalized_risk, 2)
318
+
319
+ # # Risk Category
320
+ # if normalized_risk >= 4:
321
+ # result["risk_category"] = "Critical"
322
+ # elif normalized_risk >= 3:
323
+ # result["risk_category"] = "Advanced"
324
+ # elif normalized_risk >= 2:
325
+ # result["risk_category"] = "Progressive"
326
+ # else:
327
+ # result["risk_category"] = "Early Stage"
328
+
329
+ # # Urgency
330
+ # if normalized_risk >= 3.5:
331
+ # result["urgency_level"] = "high"
332
+ # result["urgency_message"] = "Immediate dental consultation required (within 24-48 hours)."
333
+ # elif normalized_risk >= 2.0:
334
+ # result["urgency_level"] = "medium"
335
+ # result["urgency_message"] = "Dental appointment recommended within 1-4 weeks."
336
+ # else:
337
+ # result["urgency_level"] = "low"
338
+ # result["urgency_message"] = "Maintain oral hygiene and monitor symptoms."
339
+
340
+ # # Multi-disease interaction
341
+ # if "Calculus" in detected_conditions and "Gingivitis" in detected_conditions:
342
+ # result["clinical_overview"].append({
343
+ # "condition": "Clinical Interaction",
344
+ # "note": "Dental calculus may be contributing to gingival inflammation.",
345
+ # "impact_weight": 0
346
+ # })
347
+
348
+ # # Sort overview
349
+ # result["clinical_overview"] = sorted(result["clinical_overview"], key=lambda x: x.get("impact_weight", 0), reverse=True)
350
+
351
+ # # Primary condition
352
+ # if result["clinical_overview"]:
353
+ # result["primary_condition"] = result["clinical_overview"][0]["condition"]
354
+
355
+ # return result
356
+
357
+ # # ============================================================
358
+ # # PREDICTION FUNCTIONS
359
+ # # ============================================================
360
+ # def predict_teeth(image: np.ndarray, threshold: float = 0.5) -> dict:
361
+ # image = np.expand_dims(image, axis=0)
362
+ # score = BINARY_MODEL.predict(image, verbose=0)[0][0]
363
+
364
+ # is_teeth = score >= threshold
365
+ # confidence = score if is_teeth else 1 - score
366
+
367
+ # return {
368
+ # "is_teeth": bool(is_teeth),
369
+ # "class": BINARY_CLASSES[1] if is_teeth else BINARY_CLASSES[0],
370
+ # "confidence": float(confidence),
371
+ # "raw_score": float(score),
372
+ # "threshold": threshold
373
+ # }
374
+
375
+ # def predict_teeth_health(image_bytes: bytes) -> dict:
376
+ # image = load_image(image_bytes)
377
+ # outputs = TEETH_HEALTH_MODEL(image)
378
+
379
+ # top = outputs[0]
380
+
381
+ # return {
382
+ # "predicted_class": top["label"],
383
+ # "confidence": float(top["score"]),
384
+ # "all_predictions": outputs
385
+ # }
386
+
387
+ # def predict_disease(image: np.ndarray) -> dict:
388
+ # image = np.expand_dims(image, axis=0)
389
+ # predictions = DISEASE_MODEL.predict(image, verbose=0)[0]
390
+
391
+ # top_index = np.argmax(predictions)
392
+ # confidence = predictions[top_index]
393
+
394
+ # top_predictions = sorted(
395
+ # [
396
+ # {
397
+ # "class": DISEASE_CLASSES[i],
398
+ # "confidence": float(predictions[i])
399
+ # }
400
+ # for i in range(len(DISEASE_CLASSES))
401
+ # ],
402
+ # key=lambda x: x["confidence"],
403
+ # reverse=True
404
+ # )[:3]
405
+
406
+ # return {
407
+ # "predicted_class": DISEASE_CLASSES[top_index],
408
+ # "confidence": float(confidence),
409
+ # "top_predictions": top_predictions
410
+ # }
411
+
412
+ # # ============================================================
413
+ # # MAIN PIPELINE
414
+ # # ============================================================
415
+ # def teeth_diagnosis_pipeline(image_bytes: bytes, threshold: float = 0.5) -> dict:
416
+ # # 1️⃣ Binary detection
417
+ # binary_image = preprocess_for_binary(image_bytes)
418
+ # binary_result = predict_teeth(binary_image, threshold)
419
+
420
+ # if not binary_result["is_teeth"]:
421
+ # return {
422
+ # "status": "rejected",
423
+ # "binary_result": binary_result,
424
+ # "message": "Image does not contain teeth"
425
+ # }
426
+
427
+ # # 2️⃣ Teeth Health
428
+ # health_result = predict_teeth_health(image_bytes)
429
+
430
+ # label = str(health_result.get("predicted_class", "")).lower()
431
+ # confidence = health_result.get("confidence", 0)
432
+
433
+ # if label == "good teeth" and confidence >= 0.84:
434
+ # disease_result = {
435
+ # "message": "Teeth are healthy and free of diseases",
436
+ # "predicted_class": None,
437
+ # "top_predictions": []
438
+ # }
439
+ # else:
440
+ # disease_image = preprocess_for_disease(image_bytes)
441
+ # disease_result = predict_disease(disease_image)
442
+
443
+ # return {
444
+ # "status": "success",
445
+ # "binary_result": binary_result,
446
+ # "teeth_health_result": health_result,
447
+ # "disease_result": disease_result
448
+ # }
449
+
450
+ # # ============================================================
451
+ # # API ENDPOINTS
452
+ # # ============================================================
453
+ # @app.get("/")
454
+ # def root():
455
+ # return {
456
+ # "system": "Integrated Teeth Detection & Diagnosis API",
457
+ # "pipeline": [
458
+ # "Teeth Detection",
459
+ # "Teeth Health Classification",
460
+ # "Disease Classification"
461
+ # ]
462
+ # }
463
+
464
+ # @app.post("/predict")
465
+ # async def predict(file: UploadFile = File(...), threshold: float = 0.5):
466
+ # image_bytes = await file.read()
467
+ # result = teeth_diagnosis_pipeline(image_bytes, threshold)
468
+ # result["filename"] = file.filename
469
+ # return result
470
+
471
+ # @app.post("/detect-teeth")
472
+ # async def detect_teeth(
473
+ # file: UploadFile = File(...),
474
+ # ):
475
+ # """
476
+ # Detect whether the image contains teeth or not
477
+ # """
478
+
479
+ # request_id = str(uuid.uuid4())
480
+
481
+ # try:
482
+ # image_bytes = await file.read()
483
+
484
+ # image = preprocess_for_binary(image_bytes)
485
+ # binary_result = predict_teeth(image)
486
+
487
+ # return {
488
+ # "status": "success",
489
+ # "request_id": request_id,
490
+ # "filename": file.filename,
491
+ # "is_teeth": binary_result["is_teeth"],
492
+ # "predicted_class": binary_result["class"],
493
+ # "confidence": binary_result["confidence"],
494
+ # "raw_score": binary_result["raw_score"],
495
+ # }
496
+
497
+ # except Exception:
498
+ # raise HTTPException(
499
+ # status_code=500,
500
+ # detail=f"Internal server error | request_id: {request_id}"
501
+ # )
502
+
503
+ # @app.post("/check-teeth-health")
504
+ # async def check_teeth_health(file: UploadFile = File(...)):
505
+ # image_bytes = await file.read()
506
+ # return predict_teeth_health(image_bytes)
507
+
508
+
509
+ # @app.post("/advanced-recommendations")
510
+ # async def advanced_recommendations(
511
+ # file: UploadFile = File(...),
512
+ # threshold: float = Query(0.5, ge=0.0, le=1.0),
513
+ # age: int = Query(18, ge=0, le=120),
514
+ # pain_level: int = Query(0, ge=0, le=10),
515
+ # bleeding: bool = False,
516
+ # ):
517
+ # request_id = str(uuid.uuid4())
518
+
519
+ # try:
520
+ # image_bytes = await file.read()
521
+ # diagnosis = teeth_diagnosis_pipeline(image_bytes, threshold)
522
+
523
+ # if diagnosis.get("status") != "success":
524
+ # raise HTTPException(status_code=422, detail="Diagnosis failed.")
525
+
526
+ # top_predictions = diagnosis["disease_result"].get("top_predictions", [])
527
+
528
+ # if not top_predictions:
529
+ # return JSONResponse(
530
+ # status_code=200,
531
+ # content={
532
+ # "status": "no_disease_detected",
533
+ # "request_id": request_id,
534
+ # "summary": {
535
+ # "primary_condition": None,
536
+ # "confidence": 0,
537
+ # "confidence_level": "none",
538
+ # "overall_risk_score": 0,
539
+ # "risk_category": "Low",
540
+ # "urgency_level": "low",
541
+ # "requires_dentist": False
542
+ # },
543
+ # "general_advice": [
544
+ # "Continue regular dental checkups",
545
+ # "Maintain good oral hygiene"
546
+ # ]
547
+ # }
548
+ # )
549
+
550
+ # advanced_recs = get_weighted_recommendations(
551
+ # top_predictions, age=age, pain_level=pain_level, bleeding=bleeding
552
+ # )
553
+
554
+ # primary_conf = diagnosis["disease_result"]["confidence"]
555
+ # if primary_conf >= 0.9:
556
+ # confidence_level = "very_high"
557
+ # elif primary_conf >= 0.7:
558
+ # confidence_level = "high"
559
+ # elif primary_conf >= 0.5:
560
+ # confidence_level = "medium"
561
+ # else:
562
+ # confidence_level = "low"
563
+
564
+ # return {
565
+ # "status": "success",
566
+ # "request_id": request_id,
567
+ # "filename": file.filename,
568
+ # "summary": {
569
+ # "primary_condition": diagnosis["disease_result"]["predicted_class"],
570
+ # "confidence": primary_conf,
571
+ # "confidence_level": confidence_level,
572
+ # "overall_risk_score": advanced_recs.get("overall_risk_score"),
573
+ # "risk_category": advanced_recs.get("risk_category"),
574
+ # "urgency_level": advanced_recs.get("urgency_level"),
575
+ # "requires_dentist": advanced_recs.get("requires_dentist"),
576
+ # "show_emergency_banner": advanced_recs.get("urgency_level") == "high"
577
+ # },
578
+ # "diagnosis": {"top_predictions": top_predictions},
579
+ # "recommendations": {
580
+ # "clinical_overview": advanced_recs.get("clinical_overview"),
581
+ # "priority_treatment": advanced_recs.get("priority_treatment_plan"),
582
+ # "supportive_treatment": advanced_recs.get("supportive_treatments"),
583
+ # "home_care": advanced_recs.get("personalized_home_care"),
584
+ # "follow_up": advanced_recs.get("follow_up_recommendation"),
585
+ # "urgency_message": advanced_recs.get("urgency_message")
586
+ # }
587
+ # }
588
+
589
+ # except Exception as e:
590
+ # raise HTTPException(status_code=500, detail=f"Internal server error | request_id: {request_id}")
591
+
592
+
593
+
594
+ # # ============================================================
595
+ # # SERVER START
596
+ # # ============================================================
597
+ # if __name__ == "__main__":
598
+ # print("=" * 70)
599
+ # print("Integrated Teeth Detection & Disease Classification System")
600
+ # print("Server running at: http://localhost:8000")
601
+ # print("API Docs: http://localhost:8000/docs")
602
+ # print("=" * 70)
603
+
604
+ # uvicorn.run(app, host="0.0.0.0", port=8000)
605
+
606
+
607
+
608
+
609
+ from fastapi import FastAPI, File, Path
610
+ from fastapi.middleware.cors import CORSMiddleware
611
+ import uvicorn
612
+ import numpy as np
613
+ from io import BytesIO
614
+ from PIL import Image
615
+ import tensorflow as tf
616
+ from tensorflow.keras.applications.efficientnet import preprocess_input # type: ignore
617
+ import uuid
618
+ from fastapi import File, UploadFile, Query, HTTPException
619
+ from fastapi.responses import JSONResponse
620
+ import time
621
+ import pyheif
622
+
623
+
624
+
625
+ # HuggingFace
626
+ from transformers import pipeline
627
+ import torch
628
+ import json
629
+ import os
630
+ from pathlib import Path
631
+ from datetime import datetime
632
+
633
+
634
+
635
+
636
+ # ============================================================
637
+ # CONFIGURATION
638
+ # ============================================================
639
+ IMAGE_SIZE = 224
640
+
641
+ BINARY_MODEL_PATH = "./model_2.keras"
642
+ DISEASE_MODEL_PATH = "./LAST_model_efficent.h5"
643
+
644
+
645
+ print(os.path.exists(BINARY_MODEL_PATH))
646
+ print(os.path.isfile(BINARY_MODEL_PATH))
647
+
648
+ BASE_DIR = Path(__file__).parent
649
+ KNOWLEDGE_BASE_PATH = BASE_DIR / "knowledge_base" / "clinical_rules.json"
650
+
651
+
652
+ # HuggingFace Teeth Health Model
653
+ HF_TEETH_HEALTH_MODEL = "steven123/Check_GoodBad_Teeth"
654
+
655
+ DEVICE = 0 if torch.cuda.is_available() else -1
656
+
657
+ BINARY_CLASSES = ["not_teath", "teath"]
658
+ TEETH_HEALTH_CLASSES = ["Good Teeth", "Bad Teeth"]
659
+
660
+ DISEASE_CLASSES = [
661
+ "Calculus",
662
+ "Dental Caries",
663
+ "Gingivitis",
664
+ "Mouth Ulcer",
665
+ "Tooth Discoloration",
666
+ "Hypodontia"
667
+ ]
668
+
669
+
670
+ # ============================================================
671
+ # LOAD KNOWLEDGE BASE
672
+ # ============================================================
673
+ def load_knowledge_base():
674
+ """تحميل قاعدة المعرفة من ملف JSON"""
675
+ try:
676
+ with open(KNOWLEDGE_BASE_PATH, 'r', encoding='utf-8') as f:
677
+ return json.load(f)
678
+ except FileNotFoundError:
679
+ print(f"⚠️ Warning: Knowledge base file not found at {KNOWLEDGE_BASE_PATH}")
680
+ return {"diseases": {}, "general_rules": {}}
681
+
682
+ knowledge_base_data = load_knowledge_base()
683
+ diseases_db = knowledge_base_data.get("diseases", {})
684
+ general_rules = knowledge_base_data.get("general_rules", {})
685
+
686
+
687
+
688
+ # ============================================================
689
+ # APPLICATION INITIALIZATION
690
+ # ============================================================
691
+ app = FastAPI(
692
+ title="Integrated Teeth Detection System",
693
+ description="Binary Detection → Teeth Health → Disease Classification",
694
+ version="1.2.0"
695
+ )
696
+
697
+ app.add_middleware(
698
+ CORSMiddleware,
699
+ allow_origins=["*"],
700
+ allow_credentials=True,
701
+ allow_methods=["*"],
702
+ allow_headers=["*"],
703
+ )
704
+
705
+ print("exists:", os.path.exists(BINARY_MODEL_PATH))
706
+ print("isfile:", os.path.isfile(BINARY_MODEL_PATH))
707
+
708
+ # ============================================================
709
+ # # MODEL LOADING
710
+ # ============================================================
711
+ print("[INFO] Loading TensorFlow models...")
712
+ BINARY_MODEL = tf.keras.models.load_model(BINARY_MODEL_PATH)
713
+ DISEASE_MODEL = tf.keras.models.load_model(DISEASE_MODEL_PATH)
714
+
715
+ print("[INFO] Loading HuggingFace Teeth Health model...")
716
+ TEETH_HEALTH_MODEL = pipeline(
717
+ "image-classification",
718
+ model=HF_TEETH_HEALTH_MODEL,
719
+ device=DEVICE
720
+ )
721
+
722
+ print("[INFO] All models loaded successfully")
723
+
724
+ # ============================================================
725
+ # IMAGE PREPROCESSING
726
+ # ============================================================
727
+ def load_image(image_bytes: bytes) -> Image.Image:
728
+ """
729
+ Load any image and convert to RGB.
730
+ Supports HEIC/HEIF and standard formats (JPEG, PNG, etc.).
731
+ """
732
+ try:
733
+ # جرب نقرأ الصورة كـ HEIC/HEIF
734
+ heif_file = pyheif.read_heif(image_bytes)
735
+ image = Image.frombytes(
736
+ heif_file.mode,
737
+ heif_file.size,
738
+ heif_file.data,
739
+ "raw",
740
+ heif_file.mode,
741
+ heif_file.stride
742
+ )
743
+ return image.convert("RGB")
744
+
745
+ except pyheif.error.HeifError:
746
+ # لو مش HEIC/HEIF، افتحها كـ JPEG/PNG عادي
747
+ try:
748
+ return Image.open(BytesIO(image_bytes)).convert("RGB")
749
+ except Exception as e:
750
+ raise HTTPException(status_code=422, detail=f"Invalid or corrupted image: {str(e)}")
751
+
752
+ def preprocess_for_binary(image_bytes: bytes) -> np.ndarray:
753
+ image = load_image(image_bytes)
754
+ image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
755
+ image = np.array(image).astype(np.float32)
756
+ return image
757
+
758
+ def preprocess_for_disease(image_bytes: bytes) -> np.ndarray:
759
+ image = load_image(image_bytes)
760
+ image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
761
+ image = np.array(image).astype(np.float32)
762
+ image = preprocess_input(image)
763
+ return image
764
+
765
+ def assess_urgency(result):
766
+ return {
767
+ "level": result.get("urgency_level", "low"),
768
+ "message": result.get("urgency_message", "")
769
+ }
770
+ def combine_advice(result):
771
+ combined = []
772
+ seen = set()
773
+
774
+ for advice in result.get("personalized_home_care", []):
775
+ if advice not in seen:
776
+ combined.append(advice)
777
+ seen.add(advice)
778
+
779
+ return combined[:4]
780
+
781
+
782
+ #===========================================================
783
+ # RECOMMENDATION ENGINE
784
+ # ============================================================
785
+ def add_unique_advice(advice_list, target_list):
786
+ """
787
+ Adds advice to target_list if not already present (prevents exact duplicates)
788
+ """
789
+ if not advice_list:
790
+ return
791
+ for advice in advice_list:
792
+ if advice and advice not in target_list:
793
+ target_list.append(advice)
794
+ #============================================================'
795
+ # Advanced weighted recommendation
796
+ #============================================================
797
+ def get_weighted_recommendations(top_predictions, age: int, pain_level: int, bleeding: bool):
798
+ result = {
799
+ "timestamp": datetime.now().isoformat(),
800
+ "primary_condition": None,
801
+ "overall_risk_score": 0.0,
802
+ "risk_category": "Early Stage",
803
+ "clinical_overview": [],
804
+ "priority_treatment_plan": [],
805
+ "supportive_treatments": [],
806
+ "personalized_home_care": {"essential": [], "recommended": [], "avoid": []},
807
+ "follow_up_recommendation": [],
808
+ "requires_dentist": False,
809
+ "urgency_level": "low",
810
+ "urgency_message": ""
811
+ }
812
+
813
+ if not top_predictions:
814
+ return result
815
+
816
+ severity_scale = {"high": 3, "medium": 2, "moderate":2, "mild": 1, "low": 1,"structural": 2}
817
+ urgency_scale = {"high": 3, "medium": 2, "low": 1}
818
+ confidence_rules = general_rules.get("confidence_weighting", {})
819
+
820
+ filtered_predictions = [p for p in top_predictions if p.get("confidence", 0) > 0.05]
821
+ if not filtered_predictions:
822
+ return result
823
+
824
+ total_conf = sum(p["confidence"] for p in filtered_predictions)
825
+ total_risk_score = 0
826
+ detected_conditions = []
827
+
828
+ for pred in filtered_predictions:
829
+ disease = pred["class"]
830
+ confidence = pred["confidence"]
831
+ weight = confidence / total_conf if total_conf > 0 else 0
832
+
833
+ if disease not in diseases_db:
834
+ continue
835
+
836
+ detected_conditions.append(disease)
837
+ disease_info = diseases_db[disease]
838
+ base = disease_info.get("base_info", {})
839
+ treatments = disease_info.get("treatment_options", {}).get("primary", [])
840
+ home_advice = disease_info.get("home_advice", {})
841
+
842
+ # 🔹 Severity & urgency
843
+ severity = base.get("severity", "low")
844
+ urgency = base.get("urgency", "low")
845
+ severity_value = severity_scale.get(severity, 1)
846
+ urgency_value = urgency_scale.get(urgency, 1)
847
+
848
+ # --- Symptom-based adjustment (Improved Clinical Logic) ---
849
+ bleeding_factor = 1 if bleeding else 0
850
+ disease_category = base.get("category", "")
851
+
852
+ # Option 1: Direct multiplier (pain_level 0-10)
853
+ if disease_category in ["tooth_decay", "inflammatory"]:
854
+ severity_value += pain_level * 0.3
855
+ urgency_value += pain_level * 0.3
856
+ elif disease_category in ["soft_tissue", "mineral_deposit","developmental","aesthetic"]:
857
+ severity_value += pain_level * 0.1
858
+ urgency_value += pain_level * 0.1
859
+
860
+ # Bleeding impact
861
+ if disease_category in ["inflammatory", "tooth_decay"]:
862
+ urgency_value += bleeding_factor * 1.5
863
+ elif disease_category in ["soft_tissue", "mineral_deposit","developmental","aesthetic"]:
864
+ urgency_value += bleeding_factor * 0.4
865
+
866
+ # Age sensitivity
867
+ if age < 12 or age > 65:
868
+ urgency_value += 0.5
869
+
870
+ # حساب عامل الثقة بناءً على confidence
871
+ if confidence >= 0.8:
872
+ confidence_factor = 1.0
873
+ elif confidence >= 0.5:
874
+ confidence_factor = confidence_rules.get("medium", 0.5)
875
+ else:
876
+ confidence_factor = confidence_rules.get("low", 0.2)
877
+
878
+
879
+
880
+ disease_risk = ((severity_value * 0.6 + urgency_value * 0.4) * weight * confidence_factor)
881
+ total_risk_score += disease_risk
882
+
883
+ # Treatment level
884
+ if severity == "high":
885
+ treatment_level = "aggressive"
886
+ elif severity in ["medium", "structural"]:
887
+ treatment_level = "moderate"
888
+ else:
889
+ treatment_level = "conservative"
890
+
891
+ # Clinical overview
892
+ result["clinical_overview"].append({
893
+ "condition": disease,
894
+ "confidence_percent": round(confidence * 100, 2),
895
+ "impact_weight": round(weight, 3),
896
+ "severity": severity,
897
+ "urgency": urgency,
898
+ "treatment_level": treatment_level
899
+ })
900
+
901
+ # Treatment plans
902
+ if treatment_level == "aggressive":
903
+ for t in treatments:
904
+ add_unique_advice([t], result["priority_treatment_plan"])
905
+ elif treatment_level == "moderate":
906
+ for t in treatments[:1]:
907
+ add_unique_advice([t], result["supportive_treatments"])
908
+
909
+ # --- Home care advice (Compact & Professional) ---
910
+ essential_advice = home_advice.get("essential", [])[:2]
911
+ recommended_advice = home_advice.get("recommended", [])[:2]
912
+ avoid_advice = home_advice.get("avoid", [])[:2]
913
+
914
+ add_unique_advice(essential_advice, result["personalized_home_care"]["essential"])
915
+ add_unique_advice(recommended_advice, result["personalized_home_care"]["recommended"])
916
+ add_unique_advice(avoid_advice, result["personalized_home_care"]["avoid"])
917
+
918
+ # Build-up advice
919
+ build_up = disease_info.get("build_up_recommendation", {})
920
+ if build_up.get("applicable", False):
921
+ conditions = build_up.get("conditions", {})
922
+ for cond in conditions.values():
923
+ if confidence >= cond.get("confidence_threshold", 0.3):
924
+ materials = ", ".join(cond.get("materials", []))
925
+ reason = cond.get("reason", "")
926
+ advice_text = f"Consider build-up using {materials} ({reason})"
927
+ add_unique_advice([advice_text], result["personalized_home_care"]["recommended"])
928
+
929
+ # Dentist requirement
930
+ if base.get("requires_dentist", False):
931
+ result["requires_dentist"] = True
932
+
933
+ # Follow up
934
+ follow_up = disease_info.get("follow_up")
935
+ if follow_up:
936
+ add_unique_advice([follow_up], result["follow_up_recommendation"])
937
+
938
+ # Normalize risk
939
+ normalized_risk = min(total_risk_score, 5)
940
+ result["overall_risk_score"] = round(normalized_risk, 2)
941
+
942
+ # Risk Category
943
+ if normalized_risk >= 4:
944
+ result["risk_category"] = "Critical"
945
+ elif normalized_risk >= 3:
946
+ result["risk_category"] = "Advanced"
947
+ elif normalized_risk >= 2:
948
+ result["risk_category"] = "Progressive"
949
+ else:
950
+ result["risk_category"] = "Early Stage"
951
+
952
+ # Urgency
953
+ if normalized_risk >= 3.5:
954
+ result["urgency_level"] = "high"
955
+ result["urgency_message"] = "Immediate dental consultation required (within 24-48 hours)."
956
+ elif normalized_risk >= 2.0:
957
+ result["urgency_level"] = "medium"
958
+ result["urgency_message"] = "Dental appointment recommended within 1-4 weeks."
959
+ else:
960
+ result["urgency_level"] = "low"
961
+ result["urgency_message"] = "Maintain oral hygiene and monitor symptoms."
962
+
963
+ # Multi-disease interaction
964
+ if "Calculus" in detected_conditions and "Gingivitis" in detected_conditions:
965
+ result["clinical_overview"].append({
966
+ "condition": "Clinical Interaction",
967
+ "note": "Dental calculus may be contributing to gingival inflammation.",
968
+ "impact_weight": 0
969
+ })
970
+
971
+ # Sort overview
972
+ result["clinical_overview"] = sorted(result["clinical_overview"], key=lambda x: x.get("impact_weight", 0), reverse=True)
973
+
974
+ # Primary condition
975
+ if result["clinical_overview"]:
976
+ result["primary_condition"] = result["clinical_overview"][0]["condition"]
977
+
978
+ return result
979
+
980
+ # ============================================================
981
+ # PREDICTION FUNCTIONS
982
+ # ============================================================
983
+ def predict_teeth(image: np.ndarray, threshold: float = 0.5) -> dict:
984
+ image = np.expand_dims(image, axis=0)
985
+ score = BINARY_MODEL.predict(image, verbose=0)[0][0]
986
+
987
+ is_teeth = score >= threshold
988
+ confidence = score if is_teeth else 1 - score
989
+
990
+ return {
991
+ "is_teeth": bool(is_teeth),
992
+ "class": BINARY_CLASSES[1] if is_teeth else BINARY_CLASSES[0],
993
+ "confidence": float(confidence),
994
+ "raw_score": float(score),
995
+ "threshold": threshold
996
+ }
997
+
998
+ def predict_teeth_health(image_bytes: bytes) -> dict:
999
+ image = load_image(image_bytes)
1000
+ outputs = TEETH_HEALTH_MODEL(image)
1001
+
1002
+ top = outputs[0]
1003
+
1004
+ return {
1005
+ "predicted_class": top["label"],
1006
+ "confidence": float(top["score"]),
1007
+ "all_predictions": outputs
1008
+ }
1009
+
1010
+ def predict_disease(image: np.ndarray) -> dict:
1011
+ image = np.expand_dims(image, axis=0)
1012
+ predictions = DISEASE_MODEL.predict(image, verbose=0)[0]
1013
+
1014
+ top_index = np.argmax(predictions)
1015
+ confidence = predictions[top_index]
1016
+
1017
+ top_predictions = sorted(
1018
+ [
1019
+ {
1020
+ "class": DISEASE_CLASSES[i],
1021
+ "confidence": float(predictions[i])
1022
+ }
1023
+ for i in range(len(DISEASE_CLASSES))
1024
+ ],
1025
+ key=lambda x: x["confidence"],
1026
+ reverse=True
1027
+ )[:3]
1028
+
1029
+ return {
1030
+ "predicted_class": DISEASE_CLASSES[top_index],
1031
+ "confidence": float(confidence),
1032
+ "top_predictions": top_predictions
1033
+ }
1034
+
1035
+ # ============================================================
1036
+ # MAIN PIPELINE
1037
+ # ============================================================
1038
+ def teeth_diagnosis_pipeline(image_bytes: bytes, threshold: float = 0.5) -> dict:
1039
+ # 1️⃣ Binary detection
1040
+ binary_image = preprocess_for_binary(image_bytes)
1041
+ binary_result = predict_teeth(binary_image, threshold)
1042
+
1043
+ if not binary_result["is_teeth"]:
1044
+ return {
1045
+ "status": "rejected",
1046
+ "binary_result": binary_result,
1047
+ "message": "Image does not contain teeth"
1048
+ }
1049
+
1050
+ # 2️⃣ Teeth Health
1051
+ health_result = predict_teeth_health(image_bytes)
1052
+
1053
+ label = str(health_result.get("predicted_class", "")).lower()
1054
+ confidence = health_result.get("confidence", 0)
1055
+
1056
+ if label == "good teeth" and confidence >= 0.84:
1057
+ disease_result = {
1058
+ "message": "Teeth are healthy and free of diseases",
1059
+ "predicted_class": None,
1060
+ "top_predictions": []
1061
+ }
1062
+ else:
1063
+ disease_image = preprocess_for_disease(image_bytes)
1064
+ disease_result = predict_disease(disease_image)
1065
+
1066
+ return {
1067
+ "status": "success",
1068
+ "binary_result": binary_result,
1069
+ "teeth_health_result": health_result,
1070
+ "disease_result": disease_result
1071
+ }
1072
+
1073
+ # ============================================================
1074
+ # API ENDPOINTS
1075
+ # ============================================================
1076
+ @app.get("/")
1077
+ def root():
1078
+ return {
1079
+ "system": "Integrated Teeth Detection & Diagnosis API",
1080
+ "pipeline": [
1081
+ "Teeth Detection",
1082
+ "Teeth Health Classification",
1083
+ "Disease Classification"
1084
+ ]
1085
+ }
1086
+
1087
+ @app.post("/predict")
1088
+ async def predict(file: UploadFile = File(...), threshold: float = 0.5):
1089
+ image_bytes = await file.read()
1090
+ result = teeth_diagnosis_pipeline(image_bytes, threshold)
1091
+ result["filename"] = file.filename
1092
+ return result
1093
+
1094
+ @app.post("/detect-teeth")
1095
+ async def detect_teeth(
1096
+ file: UploadFile = File(...),
1097
+ ):
1098
+ """
1099
+ Detect whether the image contains teeth or not
1100
+ """
1101
+
1102
+ request_id = str(uuid.uuid4())
1103
+
1104
+ try:
1105
+ image_bytes = await file.read()
1106
+
1107
+ image = preprocess_for_binary(image_bytes)
1108
+ binary_result = predict_teeth(image)
1109
+
1110
+ return {
1111
+ "status": "success",
1112
+ "request_id": request_id,
1113
+ "filename": file.filename,
1114
+ "is_teeth": binary_result["is_teeth"],
1115
+ "predicted_class": binary_result["class"],
1116
+ "confidence": binary_result["confidence"],
1117
+ "raw_score": binary_result["raw_score"],
1118
+ }
1119
+
1120
+ except Exception:
1121
+ raise HTTPException(
1122
+ status_code=500,
1123
+ detail=f"Internal server error | request_id: {request_id}"
1124
+ )
1125
+
1126
+ @app.post("/check-teeth-health")
1127
+ async def check_teeth_health(file: UploadFile = File(...)):
1128
+ image_bytes = await file.read()
1129
+ return predict_teeth_health(image_bytes)
1130
+
1131
+
1132
+ @app.post("/advanced-recommendations")
1133
+ async def advanced_recommendations(
1134
+ file: UploadFile = File(...),
1135
+ threshold: float = Query(0.5, ge=0.0, le=1.0),
1136
+ age: int = Query(18, ge=0, le=120),
1137
+ pain_level: int = Query(0, ge=0, le=10),
1138
+ bleeding: bool = False,
1139
+ ):
1140
+ request_id = str(uuid.uuid4())
1141
+
1142
+ try:
1143
+ image_bytes = await file.read()
1144
+ diagnosis = teeth_diagnosis_pipeline(image_bytes, threshold)
1145
+
1146
+ if diagnosis.get("status") != "success":
1147
+ raise HTTPException(status_code=422, detail="Diagnosis failed.")
1148
+
1149
+ top_predictions = diagnosis["disease_result"].get("top_predictions", [])
1150
+
1151
+ if not top_predictions:
1152
+ return JSONResponse(
1153
+ status_code=200,
1154
+ content={
1155
+ "status": "no_disease_detected",
1156
+ "request_id": request_id,
1157
+ "summary": {
1158
+ "primary_condition": None,
1159
+ "confidence": 0,
1160
+ "confidence_level": "none",
1161
+ "overall_risk_score": 0,
1162
+ "risk_category": "Low",
1163
+ "urgency_level": "low",
1164
+ "requires_dentist": False
1165
+ },
1166
+ "general_advice": [
1167
+ "Continue regular dental checkups",
1168
+ "Maintain good oral hygiene"
1169
+ ]
1170
+ }
1171
+ )
1172
+
1173
+ advanced_recs = get_weighted_recommendations(
1174
+ top_predictions, age=age, pain_level=pain_level, bleeding=bleeding
1175
+ )
1176
+
1177
+ primary_conf = diagnosis["disease_result"]["confidence"]
1178
+ if primary_conf >= 0.9:
1179
+ confidence_level = "very_high"
1180
+ elif primary_conf >= 0.7:
1181
+ confidence_level = "high"
1182
+ elif primary_conf >= 0.5:
1183
+ confidence_level = "medium"
1184
+ else:
1185
+ confidence_level = "low"
1186
+
1187
+ return {
1188
+ "status": "success",
1189
+ "request_id": request_id,
1190
+ "filename": file.filename,
1191
+ "summary": {
1192
+ "primary_condition": diagnosis["disease_result"]["predicted_class"],
1193
+ "confidence": primary_conf,
1194
+ "confidence_level": confidence_level,
1195
+ "overall_risk_score": advanced_recs.get("overall_risk_score"),
1196
+ "risk_category": advanced_recs.get("risk_category"),
1197
+ "urgency_level": advanced_recs.get("urgency_level"),
1198
+ "requires_dentist": advanced_recs.get("requires_dentist"),
1199
+ "show_emergency_banner": advanced_recs.get("urgency_level") == "high"
1200
+ },
1201
+ "diagnosis": {"top_predictions": top_predictions},
1202
+ "recommendations": {
1203
+ "clinical_overview": advanced_recs.get("clinical_overview"),
1204
+ "priority_treatment": advanced_recs.get("priority_treatment_plan"),
1205
+ "supportive_treatment": advanced_recs.get("supportive_treatments"),
1206
+ "home_care": advanced_recs.get("personalized_home_care"),
1207
+ "follow_up": advanced_recs.get("follow_up_recommendation"),
1208
+ "urgency_message": advanced_recs.get("urgency_message")
1209
+ }
1210
+ }
1211
+
1212
+ except Exception as e:
1213
+ raise HTTPException(status_code=500, detail=f"Internal server error | request_id: {request_id}")
1214
+
1215
+
1216
+
1217
+ # ============================================================
1218
+ # SERVER START
1219
+ # ============================================================
1220
+ if __name__ == "__main__":
1221
+ print("=" * 70)
1222
+ print("Integrated Teeth Detection & Disease Classification System")
1223
+ print("Server running at: http://localhost:8000")
1224
+ print("API Docs: http://localhost:8000/docs")
1225
+ print("=" * 70)
1226
+
1227
+ uvicorn.run(app, host="0.0.0.0", port=7860)
1228
+
1229
+
1230
+
1231
+
1232
+
1233
+
1234
+
1235
+
1236
+
1237
+
1238
+
1239
+
1240
+
1241
+
1242
+
1243
+
1244
+
1245
+
1246
+
1247
+
1248
+
1249
+
1250
+
1251
+
1252
+
1253
+
1254
+
1255
+
1256
+
1257
+
1258
+
1259
+
1260
+
1261
+
1262
+
1263
+
1264
+
1265
+
1266
+
1267
+
1268
+
1269
+
1270
+
1271
+
1272
+
1273
+
1274
+
1275
+
1276
+
1277
+
1278
+
1279
+
1280
+
1281
+
1282
+
1283
+
1284
+
1285
+
1286
+
1287
+
1288
+
1289
+
1290
+
1291
+
1292
+
1293
+
1294
+
1295
+
1296
+
1297
+
1298
+
1299
+
1300
+
1301
+
1302
+
1303
+
1304
+
1305
+
1306
+
1307
+
1308
+
1309
+
1310
+
1311
+
1312
+
1313
+
1314
+
1315
+
1316
+
1317
+
1318
+
1319
+
1320
+
1321
+
1322
+
1323
+
1324
+
1325
+
knowledge_base/clinical_rules.json ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "diseases": {
3
+ "Calculus": {
4
+ "base_info": {
5
+ "severity": "moderate",
6
+ "urgency": "medium",
7
+ "requires_dentist": true,
8
+ "category": "mineral_deposit"
9
+ },
10
+ "treatment_options": {
11
+ "primary": [
12
+ "Professional scaling and polishing",
13
+ "Deep cleaning procedure"
14
+ ],
15
+ "alternative": ["Antibacterial mouthwash", "Professional prophylaxis"],
16
+ "contraindications": ["Bleeding disorders", "Recent oral surgery"]
17
+ },
18
+ "home_advice": {
19
+ "essential": [
20
+ "Brush twice daily with tartar-control toothpaste",
21
+ "Floss daily to prevent buildup between teeth",
22
+ "Use electric toothbrush for better plaque removal"
23
+ ],
24
+ "recommended": [
25
+ "Use water flosser for hard-to-reach areas",
26
+ "Salt water rinses twice weekly"
27
+ ],
28
+ "avoid": [
29
+ "Tobacco products",
30
+ "Excessive coffee/tea (can stain calculus)"
31
+ ]
32
+ },
33
+ "build_up_recommendation": {
34
+ "applicable": false,
35
+ "reason": "Calculus requires removal, not build-up",
36
+ "alternative": "Professional cleaning"
37
+ },
38
+ "follow_up": "Professional cleaning every 6 months or sooner if prone to buildup"
39
+ },
40
+
41
+ "Dental Caries": {
42
+ "base_info": {
43
+ "severity": "high",
44
+ "urgency": "high",
45
+ "requires_dentist": true,
46
+ "category": "tooth_decay"
47
+ },
48
+ "treatment_options": {
49
+ "primary": ["Dental filling", "Root canal if advanced"],
50
+ "alternative": [
51
+ "Silver diamine fluoride",
52
+ "Hall technique (for children)"
53
+ ],
54
+ "contraindications": ["Severe pulpitis", "Vertical root fracture"]
55
+ },
56
+ "home_advice": {
57
+ "essential": [
58
+ "Avoid sugary foods and drinks between meals",
59
+ "Use fluoride toothpaste with 1350-1500ppm fluoride",
60
+ "Brush for 2 minutes twice daily"
61
+ ],
62
+ "recommended": [
63
+ "Use fluoride mouthwash before bed",
64
+ "Chew sugar-free gum after meals"
65
+ ],
66
+ "avoid": [
67
+ "Sticky candies and dried fruits",
68
+ "Carbonated drinks",
69
+ "Frequent snacking"
70
+ ]
71
+ },
72
+ "build_up_recommendation": {
73
+ "applicable": true,
74
+ "conditions": {
75
+ "early_caries": {
76
+ "confidence_threshold": 0.3,
77
+ "materials": ["Composite resin", "Glass ionomer"],
78
+ "reason": "Early caries can be treated with conservative build-up"
79
+ },
80
+ "immature_tooth": {
81
+ "confidence_threshold": 0.2,
82
+ "materials": ["Composite resin", "Component"],
83
+ "reason": "Immature teeth benefit from build-up to protect pulp"
84
+ },
85
+ "erupting_tooth": {
86
+ "confidence_threshold": 0.15,
87
+ "materials": ["Glass ionomer", "Composite"],
88
+ "reason": "Erupting teeth require build-up to guide proper development"
89
+ }
90
+ },
91
+ "contraindications": ["Deep caries near pulp", "Active infection"]
92
+ },
93
+ "follow_up": "Return in 6 months or sooner if pain develops"
94
+ },
95
+
96
+ "Hypodontia": {
97
+ "base_info": {
98
+ "severity": "structural",
99
+ "urgency": "medium",
100
+ "requires_dentist": true,
101
+ "category": "developmental"
102
+ },
103
+ "treatment_options": {
104
+ "primary": [
105
+ "Orthodontic evaluation",
106
+ "Space management",
107
+ "Build-up restoration for developing teeth"
108
+ ],
109
+ "alternative": [
110
+ "Dental implants (adults)",
111
+ "Resin-bonded bridge",
112
+ "Removable partial denture"
113
+ ],
114
+ "multidisciplinary": ["Orthodontist", "Pedodontist", "Prosthodontist"]
115
+ },
116
+ "home_advice": {
117
+ "essential": [
118
+ "Maintain excellent oral hygiene around missing areas",
119
+ "Regular dental checkups every 6 months",
120
+ "Keep adjacent teeth clean to prevent shifting"
121
+ ],
122
+ "recommended": [
123
+ "Consider orthodontic consultation",
124
+ "Use fluoridated toothpaste for remaining teeth"
125
+ ],
126
+ "age_specific": {
127
+ "child_6_12": [
128
+ "Space maintainers may be needed",
129
+ "Monitor eruption of permanent teeth",
130
+ "Avoid hard foods on adjacent teeth"
131
+ ],
132
+ "teen_13_18": [
133
+ "Discuss orthodontic options",
134
+ "Consider temporary build-up",
135
+ "Plan for future implants"
136
+ ],
137
+ "adult_19_plus": [
138
+ "Evaluate for implants or bridges",
139
+ "Consider bone grafting if implant planned",
140
+ "Regular assessment of adjacent teeth"
141
+ ]
142
+ }
143
+ },
144
+ "build_up_recommendation": {
145
+ "applicable": true,
146
+ "priority": "high_for_developing",
147
+ "age_ranges": [
148
+ {
149
+ "min": 6,
150
+ "max": 12,
151
+ "priority": "essential",
152
+ "reason": "Primary/transitional dentition needs build-up for space maintenance",
153
+ "materials": ["Composite build-up", "Space maintainer"]
154
+ },
155
+ {
156
+ "min": 13,
157
+ "max": 18,
158
+ "priority": "recommended",
159
+ "reason": "Build-up recommended until growth complete",
160
+ "materials": ["Composite", "Temporary build-up"]
161
+ },
162
+ {
163
+ "min": 19,
164
+ "max": 100,
165
+ "priority": "consider",
166
+ "reason": "Build-up temporary solution, fixed restoration preferred",
167
+ "materials": ["Composite", "Temporary until definitive treatment"]
168
+ }
169
+ ],
170
+ "clinical_notes": "For erupting/developing teeth, build-up is ALWAYS preferred over fixed restoration"
171
+ },
172
+ "follow_up": "Every 6 months with radiographic monitoring of adjacent teeth"
173
+ },
174
+
175
+ "Gingivitis": {
176
+ "base_info": {
177
+ "severity": "mild",
178
+ "urgency": "low",
179
+ "requires_dentist": false,
180
+ "category": "inflammatory"
181
+ },
182
+ "treatment_options": {
183
+ "primary": [
184
+ "Improved oral hygiene",
185
+ "Professional cleaning if persistent"
186
+ ],
187
+ "alternative": [
188
+ "Antiseptic mouthwash",
189
+ "Chlorhexidine gel (short term)"
190
+ ],
191
+ "contraindications": ["Allergy to mouthwash ingredients"]
192
+ },
193
+ "home_advice": {
194
+ "essential": [
195
+ "Brush twice daily with soft-bristled toothbrush",
196
+ "Floss daily - this is crucial for gum health",
197
+ "Use gentle circular motions, not aggressive scrubbing"
198
+ ],
199
+ "recommended": [
200
+ "Use antiseptic mouthwash (alcohol-free preferred)",
201
+ "Salt water rinses (1 tsp salt in warm water) twice daily"
202
+ ],
203
+ "avoid": [
204
+ "Hard toothbrushes",
205
+ "Aggressive brushing that causes bleeding",
206
+ "Tobacco products"
207
+ ]
208
+ },
209
+ "build_up_recommendation": {
210
+ "applicable": false,
211
+ "reason": "Gingivitis affects gums, not tooth structure"
212
+ },
213
+ "follow_up": "Improvement expected in 2 weeks; see dentist if persists"
214
+ },
215
+
216
+ "Tooth Discoloration": {
217
+ "base_info": {
218
+ "severity": "mild",
219
+ "urgency": "low",
220
+ "requires_dentist": false,
221
+ "category": "aesthetic"
222
+ },
223
+ "treatment_options": {
224
+ "primary": ["Professional cleaning", "Whitening treatments"],
225
+ "alternative": [
226
+ "Composite veneers",
227
+ "Porcelain veneers",
228
+ "Microabrasion"
229
+ ],
230
+ "contraindications": ["Pregnancy", "Severe sensitivity", "Caries"]
231
+ },
232
+ "home_advice": {
233
+ "essential": [
234
+ "Reduce consumption of staining foods (coffee, tea, red wine)",
235
+ "Rinse with water after consuming staining substances",
236
+ "Use whitening toothpaste with mild abrasives"
237
+ ],
238
+ "recommended": [
239
+ "Consider professional whitening for better results",
240
+ "Maintain excellent oral hygiene"
241
+ ],
242
+ "avoid": [
243
+ "Smoking and tobacco products",
244
+ "Excessive acidic foods that erode enamel",
245
+ "Overuse of whitening products"
246
+ ]
247
+ },
248
+ "build_up_recommendation": {
249
+ "applicable": true,
250
+ "conditions": {
251
+ "severe_discoloration": {
252
+ "confidence_threshold": 0.6,
253
+ "materials": ["Composite veneer", "Porcelain veneer"],
254
+ "reason": "Severe discoloration may require build-up/veneer"
255
+ },
256
+ "structural_damage": {
257
+ "confidence_threshold": 0.4,
258
+ "materials": ["Composite build-up"],
259
+ "reason": "Build-up can restore both structure and color"
260
+ }
261
+ }
262
+ },
263
+ "follow_up": "As desired for cosmetic improvement"
264
+ },
265
+
266
+ "Mouth Ulcer": {
267
+ "base_info": {
268
+ "severity": "mild",
269
+ "urgency": "low",
270
+ "requires_dentist": false,
271
+ "category": "soft_tissue"
272
+ },
273
+ "treatment_options": {
274
+ "primary": ["Topical analgesics", "Avoid irritants"],
275
+ "alternative": [
276
+ "Prescription mouthwash",
277
+ "Corticosteroid gel (severe)"
278
+ ],
279
+ "contraindications": ["Immunosuppression", "Infection"]
280
+ },
281
+ "home_advice": {
282
+ "essential": [
283
+ "Salt water rinses (1 tsp salt in warm water) 3-4 times daily",
284
+ "Avoid spicy, acidic, or rough foods",
285
+ "Use soft toothbrush carefully around ulcer"
286
+ ],
287
+ "recommended": [
288
+ "Apply topical gel for pain",
289
+ "Rinse with alcohol-free mouthwash"
290
+ ],
291
+ "avoid": [
292
+ "Hot foods and beverages",
293
+ "Citrus fruits and juices",
294
+ "Crunchy foods (chips, nuts)"
295
+ ]
296
+ },
297
+ "build_up_recommendation": {
298
+ "applicable": false,
299
+ "reason": "Ulcers are soft tissue condition"
300
+ },
301
+ "follow_up": "Should heal within 7-14 days; see dentist if persists beyond 3 weeks"
302
+ }
303
+ },
304
+
305
+ "general_rules": {
306
+ "confidence_weighting": {
307
+ "high": 0.8,
308
+ "medium": 0.5,
309
+ "low": 0.2
310
+ },
311
+ "treatment_urgency": {
312
+ "high": "See dentist within 24-48 hours",
313
+ "medium": "Schedule appointment within 2 weeks",
314
+ "low": "Monitor and schedule routine checkup"
315
+ },
316
+ "build_up_indications": [
317
+ "Developing dentition (age < 18)",
318
+ "Partially erupted teeth",
319
+ "Minimal to moderate tooth structure loss",
320
+ "Temporary restoration needed",
321
+ "Economic considerations"
322
+ ],
323
+ "fixed_indications": [
324
+ "Fully developed dentition (age ≥ 18)",
325
+ "Extensive tooth structure loss",
326
+ "Complete root formation",
327
+ "Long-term definitive solution needed",
328
+ "Aesthetic demands in anterior teeth"
329
+ ]
330
+ }
331
+ }
requirements.txt ADDED
Binary file (146 Bytes). View file