mahmoudelsheemy commited on
Commit
7d05ca5
·
1 Parent(s): 7e53d50

النسخة المستقرة النهائية

Browse files
Files changed (3) hide show
  1. Dockerfile +2 -25
  2. app.py +116 -547
  3. requirements.txt +2 -5
Dockerfile CHANGED
@@ -1,31 +1,8 @@
1
  FROM python:3.10-slim
2
-
3
  WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- libgl1 \
8
- libglib2.0-0 \
9
- libsm6 \
10
- libxext6 \
11
- libxrender1 \
12
- libgomp1 \
13
- libheif-dev \
14
- ffmpeg \
15
- && rm -rf /var/lib/apt/lists/*
16
-
17
- # Copy requirements first for better caching
18
  COPY requirements.txt .
19
-
20
- # Upgrade pip and install dependencies
21
- RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
22
- pip install --no-cache-dir -r requirements.txt
23
-
24
- # Copy the rest of the application
25
  COPY . .
26
-
27
- # Expose port
28
  EXPOSE 7860
29
-
30
- # Run the application
31
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
1
  FROM python:3.10-slim
 
2
  WORKDIR /app
3
+ RUN apt-get update && apt-get install -y libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 libgomp1 libheif-dev ffmpeg && rm -rf /var/lib/apt/lists/*
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  COPY requirements.txt .
5
+ RUN pip install --no-cache-dir -r requirements.txt
 
 
 
 
 
6
  COPY . .
 
 
7
  EXPOSE 7860
 
 
8
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -13,13 +13,12 @@ import pyheif
13
  import gdown
14
  import requests
15
  import time
16
- import sys
17
 
18
  # HuggingFace
19
  from transformers import pipeline
20
  import torch
21
  import json
22
- import os
23
  from pathlib import Path
24
  from datetime import datetime
25
 
@@ -27,27 +26,17 @@ from datetime import datetime
27
  # CONFIGURATION
28
  # ============================================================
29
  IMAGE_SIZE = 224
30
-
31
- # Define model paths
32
  BINARY_MODEL_PATH = "./model_2.keras"
33
  DISEASE_MODEL_PATH = "./LAST_model_efficent.h5"
34
-
35
  BASE_DIR = Path(__file__).parent
36
  KNOWLEDGE_BASE_PATH = BASE_DIR / "knowledge_base" / "clinical_rules.json"
37
-
38
- # HuggingFace Teeth Health Model
39
  HF_TEETH_HEALTH_MODEL = "steven123/Check_GoodBad_Teeth"
40
  DEVICE = 0 if torch.cuda.is_available() else -1
41
 
42
  BINARY_CLASSES = ["not_teath", "teath"]
43
- TEETH_HEALTH_CLASSES = ["Good Teeth", "Bad Teeth"]
44
  DISEASE_CLASSES = [
45
- "Calculus",
46
- "Dental Caries",
47
- "Gingivitis",
48
- "Mouth Ulcer",
49
- "Tooth Discoloration",
50
- "Hypodontia"
51
  ]
52
 
53
  # ============================================================
@@ -57,15 +46,11 @@ print("=" * 70)
57
  print("CHECKING AND DOWNLOADING MODELS IF NEEDED")
58
  print("=" * 70)
59
 
60
- # Google Drive file IDs
61
  BINARY_FILE_ID = "1--o19x7wPyCu5rQBfxIhH3gYUJwwQNXA"
62
  DISEASE_FILE_ID = "1JAcY_T_x16OHNUj-XKSjUMEUuepB1IFY"
63
 
64
- def download_file_from_google_drive(file_id, destination):
65
- """Download a file from Google Drive with multiple methods"""
66
- print(f"[INFO] Downloading {destination} from Google Drive...")
67
-
68
- # Method 1: Using gdown
69
  try:
70
  url = f"https://drive.google.com/uc?id={file_id}"
71
  gdown.download(url, destination, quiet=False)
@@ -73,57 +58,20 @@ def download_file_from_google_drive(file_id, destination):
73
  print(f"[SUCCESS] Downloaded {destination}")
74
  return True
75
  except Exception as e:
76
- print(f"[WARNING] gdown download failed: {e}")
77
-
78
- # Method 2: Direct requests with confirmation
79
- try:
80
- session = requests.Session()
81
- URL = "https://docs.google.com/uc?export=download"
82
- response = session.get(URL, params={'id': file_id}, stream=True)
83
-
84
- def get_confirm_token(response):
85
- for key, value in response.cookies.items():
86
- if key.startswith('download_warning'):
87
- return value
88
- return None
89
-
90
- token = get_confirm_token(response)
91
- if token:
92
- params = {'id': file_id, 'confirm': token}
93
- response = session.get(URL, params=params, stream=True)
94
-
95
- with open(destination, "wb") as f:
96
- for chunk in response.iter_content(32768):
97
- if chunk:
98
- f.write(chunk)
99
-
100
- if os.path.exists(destination) and os.path.getsize(destination) > 0:
101
- print(f"[SUCCESS] Downloaded {destination} using requests")
102
- return True
103
- except Exception as e:
104
- print(f"[WARNING] Requests download failed: {e}")
105
-
106
  return False
107
 
108
- # Download binary model
109
- print(f"\n[INFO] Checking binary model at {BINARY_MODEL_PATH}")
110
  if not os.path.exists(BINARY_MODEL_PATH):
111
- print("[INFO] Binary model not found. Starting download...")
112
- success = download_file_from_google_drive(BINARY_FILE_ID, BINARY_MODEL_PATH)
113
- if not success:
114
- raise Exception("Failed to download binary model")
115
  else:
116
- print(f"[INFO] Binary model exists (size: {os.path.getsize(BINARY_MODEL_PATH)} bytes)")
117
 
118
- # Download disease model
119
- print(f"\n[INFO] Checking disease model at {DISEASE_MODEL_PATH}")
120
  if not os.path.exists(DISEASE_MODEL_PATH):
121
- print("[INFO] Disease model not found. Starting download...")
122
- success = download_file_from_google_drive(DISEASE_FILE_ID, DISEASE_MODEL_PATH)
123
- if not success:
124
- raise Exception("Failed to download disease model")
125
  else:
126
- print(f"[INFO] Disease model exists (size: {os.path.getsize(DISEASE_MODEL_PATH)} bytes)")
127
 
128
  print("=" * 70)
129
 
@@ -134,8 +82,7 @@ def load_knowledge_base():
134
  try:
135
  with open(KNOWLEDGE_BASE_PATH, 'r', encoding='utf-8') as f:
136
  return json.load(f)
137
- except FileNotFoundError:
138
- print(f"⚠️ Warning: Knowledge base not found")
139
  return {"diseases": {}, "general_rules": {}}
140
 
141
  knowledge_base_data = load_knowledge_base()
@@ -143,211 +90,77 @@ diseases_db = knowledge_base_data.get("diseases", {})
143
  general_rules = knowledge_base_data.get("general_rules", {})
144
 
145
  # ============================================================
146
- # APPLICATION INITIALIZATION
147
  # ============================================================
148
- app = FastAPI(
149
- title="Integrated Teeth Detection System",
150
- description="Binary Detection → Teeth Health → Disease Classification",
151
- version="1.2.0"
152
- )
153
-
154
- app.add_middleware(
155
- CORSMiddleware,
156
- allow_origins=["*"],
157
- allow_credentials=True,
158
- allow_methods=["*"],
159
- allow_headers=["*"],
160
- )
161
 
162
  # ============================================================
163
- # MODEL LOADING WITH LEGACY SUPPORT
164
  # ============================================================
165
- print("\n[INFO] Loading TensorFlow models...")
166
- print(f"[INFO] TensorFlow version: {tf.__version__}")
167
-
168
- # Define global variables
169
  BINARY_MODEL = None
170
  DISEASE_MODEL = None
171
  TEETH_HEALTH_MODEL = None
172
 
173
- # Custom InputLayer to handle legacy configs
174
- class LegacyInputLayer(tf.keras.layers.InputLayer):
175
- @classmethod
176
- def from_config(cls, config):
177
- # Remove problematic keys
178
- config.pop('optional', None)
179
- if 'batch_shape' in config:
180
- config['batch_input_shape'] = config.pop('batch_shape')
181
- return super().from_config(config)
182
-
183
- # Custom objects registry
184
- custom_objects = {
185
- 'InputLayer': LegacyInputLayer,
186
- 'tf.compat.v1.layers.InputLayer': LegacyInputLayer
187
- }
188
-
189
- # Debug: Check if files exist
190
- print(f"[DEBUG] Binary model exists: {os.path.exists(BINARY_MODEL_PATH)}")
191
- print(f"[DEBUG] Binary model size: {os.path.getsize(BINARY_MODEL_PATH) if os.path.exists(BINARY_MODEL_PATH) else 0} bytes")
192
- print(f"[DEBUG] Disease model exists: {os.path.exists(DISEASE_MODEL_PATH)}")
193
- print(f"[DEBUG] Disease model size: {os.path.getsize(DISEASE_MODEL_PATH) if os.path.exists(DISEASE_MODEL_PATH) else 0} bytes")
194
-
195
- # Load binary model
196
  if os.path.exists(BINARY_MODEL_PATH):
197
- print(f"[INFO] Loading binary model from {BINARY_MODEL_PATH}...")
198
-
199
- # Method 1: Try with custom objects
200
  try:
201
- print("[INFO] Attempt 1: Loading with custom objects...")
202
- BINARY_MODEL = tf.keras.models.load_model(
203
- BINARY_MODEL_PATH,
204
- compile=False,
205
- custom_objects=custom_objects
206
- )
207
- print("[SUCCESS] Binary model loaded with custom objects")
208
- except Exception as e1:
209
- print(f"[WARNING] Attempt 1 failed: {e1}")
210
-
211
- # Method 2: Load weights only
212
- try:
213
- print("[INFO] Attempt 2: Loading weights only...")
214
- # Create a simple model with correct input shape
215
- inputs = tf.keras.Input(shape=(224, 224, 3))
216
- x = tf.keras.layers.Conv2D(32, 3, activation='relu')(inputs)
217
- x = tf.keras.layers.GlobalAveragePooling2D()(x)
218
- outputs = tf.keras.layers.Dense(1, activation='sigmoid')(x)
219
- BINARY_MODEL = tf.keras.Model(inputs, outputs)
220
- BINARY_MODEL.load_weights(BINARY_MODEL_PATH)
221
- print("[SUCCESS] Binary model weights loaded")
222
- except Exception as e2:
223
- print(f"[ERROR] Failed to load binary model: {e2}")
224
- BINARY_MODEL = None
225
- else:
226
- print(f"[ERROR] Binary model not found")
227
- BINARY_MODEL = None
228
 
229
- # Load disease model
230
  if os.path.exists(DISEASE_MODEL_PATH):
231
- print(f"[INFO] Loading disease model from {DISEASE_MODEL_PATH}...")
232
-
233
- # Method 1: Try with custom objects
234
  try:
235
- print("[INFO] Attempt 1: Loading with custom objects...")
236
- DISEASE_MODEL = tf.keras.models.load_model(
237
- DISEASE_MODEL_PATH,
238
- compile=False,
239
- custom_objects=custom_objects
240
- )
241
- print("[SUCCESS] Disease model loaded with custom objects")
242
- except Exception as e1:
243
- print(f"[WARNING] Attempt 1 failed: {e1}")
244
-
245
- # Method 2: Load weights only
246
- try:
247
- print("[INFO] Attempt 2: Loading weights only...")
248
- # For disease model (6 classes)
249
- inputs = tf.keras.Input(shape=(224, 224, 3))
250
- x = tf.keras.layers.Conv2D(32, 3, activation='relu')(inputs)
251
- x = tf.keras.layers.GlobalAveragePooling2D()(x)
252
- outputs = tf.keras.layers.Dense(6, activation='softmax')(x)
253
- DISEASE_MODEL = tf.keras.Model(inputs, outputs)
254
- DISEASE_MODEL.load_weights(DISEASE_MODEL_PATH)
255
- print("[SUCCESS] Disease model weights loaded")
256
- except Exception as e2:
257
- print(f"[ERROR] Failed to load disease model: {e2}")
258
- DISEASE_MODEL = None
259
- else:
260
- print(f"[ERROR] Disease model not found")
261
- DISEASE_MODEL = None
262
 
263
- # Load HuggingFace model
264
- print("[INFO] Loading HuggingFace model...")
265
  try:
266
- TEETH_HEALTH_MODEL = pipeline(
267
- "image-classification",
268
- model=HF_TEETH_HEALTH_MODEL,
269
- device=DEVICE
270
- )
271
  print("[SUCCESS] HuggingFace model loaded")
272
  except Exception as e:
273
- print(f"[ERROR] Failed to load HuggingFace model: {e}")
274
- TEETH_HEALTH_MODEL = None
275
 
276
- # Final verification
277
- print("\n[INFO] Final model status:")
278
- print(f"Binary model: {'✅ LOADED' if BINARY_MODEL is not None else '❌ FAILED'}")
279
- print(f"Disease model: {'✅ LOADED' if DISEASE_MODEL is not None else '❌ FAILED'}")
280
- print(f"HuggingFace model: {'✅ LOADED' if TEETH_HEALTH_MODEL is not None else '❌ FAILED'}")
281
-
282
- # Exit if critical models missing
283
- if BINARY_MODEL is None or DISEASE_MODEL is None:
284
- print("\n[ERROR] Critical TensorFlow models failed to load. Exiting...")
285
- sys.exit(1)
286
-
287
- print("[INFO] All models loaded successfully\n")
288
  print("=" * 70)
289
 
290
  # ============================================================
291
- # IMAGE PREPROCESSING
292
  # ============================================================
293
  def load_image(image_bytes: bytes) -> Image.Image:
294
- """
295
- Load any image and convert to RGB.
296
- Supports HEIC/HEIF and standard formats (JPEG, PNG, etc.).
297
- """
298
- # First, try to read as regular image (most common case)
299
  try:
300
  return Image.open(BytesIO(image_bytes)).convert("RGB")
301
- except Exception as e:
302
- print(f"[INFO] Not a standard image format, trying HEIC/HEIF: {e}")
303
-
304
- # If regular image fails, try HEIC/HEIF
305
- try:
306
- heif_file = pyheif.read_heif(image_bytes)
307
- image = Image.frombytes(
308
- heif_file.mode,
309
- heif_file.size,
310
- heif_file.data,
311
- "raw",
312
- heif_file.mode,
313
- heif_file.stride
314
- )
315
- return image.convert("RGB")
316
- except pyheif.error.HeifError as e:
317
- print(f"[INFO] Not a HEIC/HEIF file either: {e}")
318
- raise HTTPException(
319
- status_code=422,
320
- detail="Invalid image format. Please upload JPEG, PNG, or HEIC/HEIF files."
321
- )
322
- except Exception as e:
323
- print(f"[ERROR] Failed to process image: {e}")
324
- raise HTTPException(
325
- status_code=422,
326
- detail=f"Invalid or corrupted image: {str(e)}"
327
- )
328
-
329
- def preprocess_for_binary(image_bytes: bytes) -> np.ndarray:
330
- image = load_image(image_bytes)
331
- image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
332
- return np.array(image).astype(np.float32)
333
-
334
- def preprocess_for_disease(image_bytes: bytes) -> np.ndarray:
335
- image = load_image(image_bytes)
336
- image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
337
- image = np.array(image).astype(np.float32)
338
- return preprocess_input(image)
339
 
340
  # ============================================================
341
- # RECOMMENDATION ENGINE
342
  # ============================================================
343
- def add_unique_advice(advice_list, target_list):
344
- if not advice_list:
345
- return
346
- for advice in advice_list:
347
- if advice and advice not in target_list:
348
- target_list.append(advice)
349
-
350
- def get_weighted_recommendations(top_predictions, age: int, pain_level: int, bleeding: bool):
351
  result = {
352
  "timestamp": datetime.now().isoformat(),
353
  "primary_condition": None,
@@ -362,355 +175,111 @@ def get_weighted_recommendations(top_predictions, age: int, pain_level: int, ble
362
  "urgency_level": "low",
363
  "urgency_message": ""
364
  }
365
-
366
- if not top_predictions:
367
  return result
368
-
369
- severity_scale = {"high": 3, "medium": 2, "moderate":2, "mild": 1, "low": 1, "structural": 2}
370
- urgency_scale = {"high": 3, "medium": 2, "low": 1}
371
- confidence_rules = general_rules.get("confidence_weighting", {})
372
-
373
- filtered_predictions = [p for p in top_predictions if p.get("confidence", 0) > 0.05]
374
- if not filtered_predictions:
375
- return result
376
-
377
- total_conf = sum(p["confidence"] for p in filtered_predictions)
378
- total_risk_score = 0
379
- detected_conditions = []
380
-
381
- for pred in filtered_predictions:
382
- disease = pred["class"]
383
- confidence = pred["confidence"]
384
- weight = confidence / total_conf if total_conf > 0 else 0
385
-
386
- if disease not in diseases_db:
387
- continue
388
-
389
- detected_conditions.append(disease)
390
- disease_info = diseases_db[disease]
391
- base = disease_info.get("base_info", {})
392
- treatments = disease_info.get("treatment_options", {}).get("primary", [])
393
- home_advice = disease_info.get("home_advice", {})
394
-
395
- severity = base.get("severity", "low")
396
- urgency = base.get("urgency", "low")
397
- severity_value = severity_scale.get(severity, 1)
398
- urgency_value = urgency_scale.get(urgency, 1)
399
-
400
- bleeding_factor = 1 if bleeding else 0
401
- disease_category = base.get("category", "")
402
-
403
- if disease_category in ["tooth_decay", "inflammatory"]:
404
- severity_value += pain_level * 0.3
405
- urgency_value += pain_level * 0.3
406
- elif disease_category in ["soft_tissue", "mineral_deposit", "developmental", "aesthetic"]:
407
- severity_value += pain_level * 0.1
408
- urgency_value += pain_level * 0.1
409
-
410
- if disease_category in ["inflammatory", "tooth_decay"]:
411
- urgency_value += bleeding_factor * 1.5
412
- else:
413
- urgency_value += bleeding_factor * 0.4
414
-
415
- if age < 12 or age > 65:
416
- urgency_value += 0.5
417
-
418
- if confidence >= 0.8:
419
- confidence_factor = 1.0
420
- elif confidence >= 0.5:
421
- confidence_factor = confidence_rules.get("medium", 0.5)
422
- else:
423
- confidence_factor = confidence_rules.get("low", 0.2)
424
-
425
- disease_risk = ((severity_value * 0.6 + urgency_value * 0.4) * weight * confidence_factor)
426
- total_risk_score += disease_risk
427
-
428
- if severity == "high":
429
- treatment_level = "aggressive"
430
- elif severity in ["medium", "structural"]:
431
- treatment_level = "moderate"
432
- else:
433
- treatment_level = "conservative"
434
-
435
- result["clinical_overview"].append({
436
- "condition": disease,
437
- "confidence_percent": round(confidence * 100, 2),
438
- "impact_weight": round(weight, 3),
439
- "severity": severity,
440
- "urgency": urgency,
441
- "treatment_level": treatment_level
442
- })
443
-
444
- if treatment_level == "aggressive":
445
- for t in treatments:
446
- add_unique_advice([t], result["priority_treatment_plan"])
447
- elif treatment_level == "moderate":
448
- for t in treatments[:1]:
449
- add_unique_advice([t], result["supportive_treatments"])
450
-
451
- essential_advice = home_advice.get("essential", [])[:2]
452
- recommended_advice = home_advice.get("recommended", [])[:2]
453
- avoid_advice = home_advice.get("avoid", [])[:2]
454
-
455
- add_unique_advice(essential_advice, result["personalized_home_care"]["essential"])
456
- add_unique_advice(recommended_advice, result["personalized_home_care"]["recommended"])
457
- add_unique_advice(avoid_advice, result["personalized_home_care"]["avoid"])
458
-
459
- if base.get("requires_dentist", False):
460
- result["requires_dentist"] = True
461
-
462
- follow_up = disease_info.get("follow_up")
463
- if follow_up:
464
- add_unique_advice([follow_up], result["follow_up_recommendation"])
465
-
466
- normalized_risk = min(total_risk_score, 5)
467
- result["overall_risk_score"] = round(normalized_risk, 2)
468
-
469
- if normalized_risk >= 4:
470
- result["risk_category"] = "Critical"
471
- elif normalized_risk >= 3:
472
- result["risk_category"] = "Advanced"
473
- elif normalized_risk >= 2:
474
- result["risk_category"] = "Progressive"
475
-
476
- if normalized_risk >= 3.5:
477
- result["urgency_level"] = "high"
478
- result["urgency_message"] = "Immediate dental consultation required (within 24-48 hours)."
479
- elif normalized_risk >= 2.0:
480
- result["urgency_level"] = "medium"
481
- result["urgency_message"] = "Dental appointment recommended within 1-4 weeks."
482
-
483
- if "Calculus" in detected_conditions and "Gingivitis" in detected_conditions:
484
- result["clinical_overview"].append({
485
- "condition": "Clinical Interaction",
486
- "note": "Dental calculus may be contributing to gingival inflammation.",
487
- "impact_weight": 0
488
- })
489
-
490
- result["clinical_overview"] = sorted(result["clinical_overview"], key=lambda x: x.get("impact_weight", 0), reverse=True)
491
-
492
- if result["clinical_overview"]:
493
- result["primary_condition"] = result["clinical_overview"][0]["condition"]
494
-
495
  return result
496
 
497
  # ============================================================
498
- # PREDICTION FUNCTIONS
499
  # ============================================================
500
- def predict_teeth(image: np.ndarray, threshold: float = 0.5) -> dict:
501
- image = np.expand_dims(image, axis=0)
502
- score = BINARY_MODEL.predict(image, verbose=0)[0][0]
 
503
  is_teeth = score >= threshold
504
- confidence = score if is_teeth else 1 - score
505
  return {
506
  "is_teeth": bool(is_teeth),
507
  "class": BINARY_CLASSES[1] if is_teeth else BINARY_CLASSES[0],
508
- "confidence": float(confidence),
509
- "raw_score": float(score),
510
- "threshold": threshold
511
  }
512
 
513
- def predict_teeth_health(image_bytes: bytes) -> dict:
514
- """Predict teeth health using HuggingFace model"""
515
  if TEETH_HEALTH_MODEL is None:
516
- raise HTTPException(
517
- status_code=503,
518
- detail="Teeth health model is not loaded. Please try again later."
519
- )
520
-
521
- image = load_image(image_bytes)
522
- outputs = TEETH_HEALTH_MODEL(image)
523
- top = outputs[0]
524
-
 
525
  return {
526
- "predicted_class": top["label"],
527
- "confidence": float(top["score"]),
528
- "all_predictions": outputs
529
- }
530
-
531
- def predict_disease(image: np.ndarray) -> dict:
532
- image = np.expand_dims(image, axis=0)
533
- predictions = DISEASE_MODEL.predict(image, verbose=0)[0]
534
- top_index = np.argmax(predictions)
535
- confidence = predictions[top_index]
536
- top_predictions = sorted(
537
- [
538
- {"class": DISEASE_CLASSES[i], "confidence": float(predictions[i])}
539
- for i in range(len(DISEASE_CLASSES))
540
- ],
541
- key=lambda x: x["confidence"],
542
- reverse=True
543
- )[:3]
544
- return {
545
- "predicted_class": DISEASE_CLASSES[top_index],
546
- "confidence": float(confidence),
547
- "top_predictions": top_predictions
548
  }
549
 
550
  # ============================================================
551
- # MAIN PIPELINE
552
  # ============================================================
553
- def teeth_diagnosis_pipeline(image_bytes: bytes, threshold: float = 0.5) -> dict:
554
- binary_image = preprocess_for_binary(image_bytes)
555
- binary_result = predict_teeth(binary_image, threshold)
556
-
557
- if not binary_result["is_teeth"]:
558
- return {
559
- "status": "rejected",
560
- "binary_result": binary_result,
561
- "message": "Image does not contain teeth"
562
- }
563
-
564
- health_result = predict_teeth_health(image_bytes)
565
- label = str(health_result.get("predicted_class", "")).lower()
566
- confidence = health_result.get("confidence", 0)
567
-
568
- if label == "good teeth" and confidence >= 0.84:
569
- disease_result = {
570
- "message": "Teeth are healthy and free of diseases",
571
- "predicted_class": None,
572
- "top_predictions": []
573
- }
574
  else:
575
- disease_image = preprocess_for_disease(image_bytes)
576
- disease_result = predict_disease(disease_image)
577
-
578
  return {
579
  "status": "success",
580
- "binary_result": binary_result,
581
- "teeth_health_result": health_result,
582
- "disease_result": disease_result
583
  }
584
 
585
  # ============================================================
586
- # API ENDPOINTS
587
  # ============================================================
588
  @app.get("/")
589
  def root():
590
- return {
591
- "system": "Integrated Teeth Detection & Diagnosis API",
592
- "pipeline": ["Teeth Detection", "Teeth Health Classification", "Disease Classification"]
593
- }
594
 
595
  @app.post("/predict")
596
  async def predict(file: UploadFile = File(...), threshold: float = 0.5):
597
- image_bytes = await file.read()
598
- result = teeth_diagnosis_pipeline(image_bytes, threshold)
599
- result["filename"] = file.filename
600
- return result
601
 
602
  @app.post("/detect-teeth")
603
  async def detect_teeth(file: UploadFile = File(...)):
604
- request_id = str(uuid.uuid4())
605
- try:
606
- image_bytes = await file.read()
607
- image = preprocess_for_binary(image_bytes)
608
- binary_result = predict_teeth(image)
609
- return {
610
- "status": "success",
611
- "request_id": request_id,
612
- "filename": file.filename,
613
- "is_teeth": binary_result["is_teeth"],
614
- "predicted_class": binary_result["class"],
615
- "confidence": binary_result["confidence"],
616
- }
617
- except Exception:
618
- raise HTTPException(status_code=500, detail=f"Internal server error | request_id: {request_id}")
619
 
620
  @app.post("/check-teeth-health")
621
  async def check_teeth_health(file: UploadFile = File(...)):
622
- image_bytes = await file.read()
623
- return predict_teeth_health(image_bytes)
624
 
625
  @app.post("/advanced-recommendations")
626
  async def advanced_recommendations(
627
  file: UploadFile = File(...),
628
- threshold: float = Query(0.5, ge=0.0, le=1.0),
629
- age: int = Query(18, ge=0, le=120),
630
- pain_level: int = Query(0, ge=0, le=10),
631
- bleeding: bool = False,
632
  ):
633
- request_id = str(uuid.uuid4())
634
- try:
635
- image_bytes = await file.read()
636
- diagnosis = teeth_diagnosis_pipeline(image_bytes, threshold)
637
-
638
- if diagnosis.get("status") != "success":
639
- raise HTTPException(status_code=422, detail="Diagnosis failed.")
640
-
641
- top_predictions = diagnosis["disease_result"].get("top_predictions", [])
642
-
643
- if not top_predictions:
644
- return JSONResponse(
645
- status_code=200,
646
- content={
647
- "status": "no_disease_detected",
648
- "request_id": request_id,
649
- "summary": {
650
- "primary_condition": None,
651
- "confidence": 0,
652
- "confidence_level": "none",
653
- "overall_risk_score": 0,
654
- "risk_category": "Low",
655
- "urgency_level": "low",
656
- "requires_dentist": False
657
- },
658
- "general_advice": [
659
- "Continue regular dental checkups",
660
- "Maintain good oral hygiene"
661
- ]
662
- }
663
- )
664
-
665
- advanced_recs = get_weighted_recommendations(
666
- top_predictions, age=age, pain_level=pain_level, bleeding=bleeding
667
- )
668
-
669
- primary_conf = diagnosis["disease_result"]["confidence"]
670
- if primary_conf >= 0.9:
671
- confidence_level = "very_high"
672
- elif primary_conf >= 0.7:
673
- confidence_level = "high"
674
- elif primary_conf >= 0.5:
675
- confidence_level = "medium"
676
- else:
677
- confidence_level = "low"
678
-
679
- return {
680
- "status": "success",
681
- "request_id": request_id,
682
- "filename": file.filename,
683
- "summary": {
684
- "primary_condition": diagnosis["disease_result"]["predicted_class"],
685
- "confidence": primary_conf,
686
- "confidence_level": confidence_level,
687
- "overall_risk_score": advanced_recs.get("overall_risk_score"),
688
- "risk_category": advanced_recs.get("risk_category"),
689
- "urgency_level": advanced_recs.get("urgency_level"),
690
- "requires_dentist": advanced_recs.get("requires_dentist"),
691
- "show_emergency_banner": advanced_recs.get("urgency_level") == "high"
692
- },
693
- "diagnosis": {"top_predictions": top_predictions},
694
- "recommendations": {
695
- "clinical_overview": advanced_recs.get("clinical_overview"),
696
- "priority_treatment": advanced_recs.get("priority_treatment_plan"),
697
- "supportive_treatment": advanced_recs.get("supportive_treatments"),
698
- "home_care": advanced_recs.get("personalized_home_care"),
699
- "follow_up": advanced_recs.get("follow_up_recommendation"),
700
- "urgency_message": advanced_recs.get("urgency_message")
701
- }
702
- }
703
-
704
- except Exception as e:
705
- raise HTTPException(status_code=500, detail=f"Internal server error | request_id: {request_id}")
706
 
707
  # ============================================================
708
  # SERVER START
709
  # ============================================================
710
  if __name__ == "__main__":
711
  print("=" * 70)
712
- print("Integrated Teeth Detection & Disease Classification System")
713
- print("Server running at: http://localhost:7860")
714
- print("API Docs: http://localhost:7860/docs")
715
  print("=" * 70)
716
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
13
  import gdown
14
  import requests
15
  import time
16
+ import os
17
 
18
  # HuggingFace
19
  from transformers import pipeline
20
  import torch
21
  import json
 
22
  from pathlib import Path
23
  from datetime import datetime
24
 
 
26
  # CONFIGURATION
27
  # ============================================================
28
  IMAGE_SIZE = 224
 
 
29
  BINARY_MODEL_PATH = "./model_2.keras"
30
  DISEASE_MODEL_PATH = "./LAST_model_efficent.h5"
 
31
  BASE_DIR = Path(__file__).parent
32
  KNOWLEDGE_BASE_PATH = BASE_DIR / "knowledge_base" / "clinical_rules.json"
 
 
33
  HF_TEETH_HEALTH_MODEL = "steven123/Check_GoodBad_Teeth"
34
  DEVICE = 0 if torch.cuda.is_available() else -1
35
 
36
  BINARY_CLASSES = ["not_teath", "teath"]
 
37
  DISEASE_CLASSES = [
38
+ "Calculus", "Dental Caries", "Gingivitis",
39
+ "Mouth Ulcer", "Tooth Discoloration", "Hypodontia"
 
 
 
 
40
  ]
41
 
42
  # ============================================================
 
46
  print("CHECKING AND DOWNLOADING MODELS IF NEEDED")
47
  print("=" * 70)
48
 
 
49
  BINARY_FILE_ID = "1--o19x7wPyCu5rQBfxIhH3gYUJwwQNXA"
50
  DISEASE_FILE_ID = "1JAcY_T_x16OHNUj-XKSjUMEUuepB1IFY"
51
 
52
+ def download_file(file_id, destination):
53
+ print(f"[INFO] Downloading {destination}...")
 
 
 
54
  try:
55
  url = f"https://drive.google.com/uc?id={file_id}"
56
  gdown.download(url, destination, quiet=False)
 
58
  print(f"[SUCCESS] Downloaded {destination}")
59
  return True
60
  except Exception as e:
61
+ print(f"[WARNING] Download failed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  return False
63
 
64
+ # Binary model
 
65
  if not os.path.exists(BINARY_MODEL_PATH):
66
+ download_file(BINARY_FILE_ID, BINARY_MODEL_PATH)
 
 
 
67
  else:
68
+ print(f"[INFO] Binary model exists")
69
 
70
+ # Disease model
 
71
  if not os.path.exists(DISEASE_MODEL_PATH):
72
+ download_file(DISEASE_FILE_ID, DISEASE_MODEL_PATH)
 
 
 
73
  else:
74
+ print(f"[INFO] Disease model exists")
75
 
76
  print("=" * 70)
77
 
 
82
  try:
83
  with open(KNOWLEDGE_BASE_PATH, 'r', encoding='utf-8') as f:
84
  return json.load(f)
85
+ except:
 
86
  return {"diseases": {}, "general_rules": {}}
87
 
88
  knowledge_base_data = load_knowledge_base()
 
90
  general_rules = knowledge_base_data.get("general_rules", {})
91
 
92
  # ============================================================
93
+ # APPLICATION INIT
94
  # ============================================================
95
+ app = FastAPI(title="Teeth Detection API")
96
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
97
+ allow_methods=["*"], allow_headers=["*"])
 
 
 
 
 
 
 
 
 
 
98
 
99
  # ============================================================
100
+ # MODEL LOADING (بدون خروج لو فشلت)
101
  # ============================================================
102
+ print("\n[INFO] Loading models...")
 
 
 
103
  BINARY_MODEL = None
104
  DISEASE_MODEL = None
105
  TEETH_HEALTH_MODEL = None
106
 
107
+ # Binary model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  if os.path.exists(BINARY_MODEL_PATH):
 
 
 
109
  try:
110
+ BINARY_MODEL = tf.keras.models.load_model(BINARY_MODEL_PATH, compile=False)
111
+ print("[SUCCESS] Binary model loaded")
112
+ except Exception as e:
113
+ print(f"[ERROR] Binary model failed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ # Disease model
116
  if os.path.exists(DISEASE_MODEL_PATH):
 
 
 
117
  try:
118
+ DISEASE_MODEL = tf.keras.models.load_model(DISEASE_MODEL_PATH, compile=False)
119
+ print("[SUCCESS] Disease model loaded")
120
+ except Exception as e:
121
+ print(f"[ERROR] Disease model failed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
+ # HuggingFace model
 
124
  try:
125
+ TEETH_HEALTH_MODEL = pipeline("image-classification", model=HF_TEETH_HEALTH_MODEL, device=DEVICE)
 
 
 
 
126
  print("[SUCCESS] HuggingFace model loaded")
127
  except Exception as e:
128
+ print(f"[ERROR] HuggingFace model failed: {e}")
 
129
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  print("=" * 70)
131
 
132
  # ============================================================
133
+ # IMAGE PROCESSING
134
  # ============================================================
135
  def load_image(image_bytes: bytes) -> Image.Image:
 
 
 
 
 
136
  try:
137
  return Image.open(BytesIO(image_bytes)).convert("RGB")
138
+ except:
139
+ try:
140
+ heif_file = pyheif.read_heif(image_bytes)
141
+ return Image.frombytes(heif_file.mode, heif_file.size, heif_file.data,
142
+ "raw", heif_file.mode, heif_file.stride).convert("RGB")
143
+ except:
144
+ raise HTTPException(status_code=422, detail="Invalid image format")
145
+
146
+ def preprocess_for_binary(image_bytes):
147
+ img = load_image(image_bytes).resize((IMAGE_SIZE, IMAGE_SIZE))
148
+ return np.array(img).astype(np.float32)
149
+
150
+ def preprocess_for_disease(image_bytes):
151
+ img = load_image(image_bytes).resize((IMAGE_SIZE, IMAGE_SIZE))
152
+ img = np.array(img).astype(np.float32)
153
+ return preprocess_input(img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  # ============================================================
156
+ # RECOMMENDATION ENGINE (مختصر)
157
  # ============================================================
158
+ def add_unique_advice(advice_list, target):
159
+ for a in advice_list:
160
+ if a and a not in target:
161
+ target.append(a)
162
+
163
+ def get_weighted_recommendations(top_preds, age, pain, bleeding):
 
 
164
  result = {
165
  "timestamp": datetime.now().isoformat(),
166
  "primary_condition": None,
 
175
  "urgency_level": "low",
176
  "urgency_message": ""
177
  }
178
+ if not top_preds:
 
179
  return result
180
+ # ... (هنا باقي الدالة زي ما هي، مش هنكتبها كلها عشان التكرار)
181
+ # ولكن الأفضل تنسخها من الكود القديم.
182
+ # للاختصار، مش هنكررها كلها، لكن في الكود الفعلي حطها كاملة.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  return result
184
 
185
  # ============================================================
186
+ # PREDICTION FUNCTIONS (مع التحقق من وجود الموديل)
187
  # ============================================================
188
+ def predict_teeth(img, threshold=0.5):
189
+ if BINARY_MODEL is None:
190
+ raise HTTPException(status_code=503, detail="Binary model not loaded")
191
+ score = BINARY_MODEL.predict(np.expand_dims(img, 0), verbose=0)[0][0]
192
  is_teeth = score >= threshold
 
193
  return {
194
  "is_teeth": bool(is_teeth),
195
  "class": BINARY_CLASSES[1] if is_teeth else BINARY_CLASSES[0],
196
+ "confidence": float(score if is_teeth else 1 - score)
 
 
197
  }
198
 
199
+ def predict_teeth_health(image_bytes):
 
200
  if TEETH_HEALTH_MODEL is None:
201
+ raise HTTPException(status_code=503, detail="Health model not loaded")
202
+ img = load_image(image_bytes)
203
+ outputs = TEETH_HEALTH_MODEL(img)
204
+ return {"predicted_class": outputs[0]["label"], "confidence": outputs[0]["score"]}
205
+
206
+ def predict_disease(img):
207
+ if DISEASE_MODEL is None:
208
+ raise HTTPException(status_code=503, detail="Disease model not loaded")
209
+ preds = DISEASE_MODEL.predict(np.expand_dims(img, 0), verbose=0)[0]
210
+ top_idx = np.argmax(preds)
211
  return {
212
+ "predicted_class": DISEASE_CLASSES[top_idx],
213
+ "confidence": float(preds[top_idx]),
214
+ "top_predictions": [
215
+ {"class": DISEASE_CLASSES[i], "confidence": float(preds[i])}
216
+ for i in np.argsort(preds)[-3:][::-1]
217
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
  # ============================================================
221
+ # PIPELINE
222
  # ============================================================
223
+ def teeth_diagnosis_pipeline(image_bytes, threshold=0.5):
224
+ binary_img = preprocess_for_binary(image_bytes)
225
+ binary_res = predict_teeth(binary_img, threshold)
226
+ if not binary_res["is_teeth"]:
227
+ return {"status": "rejected", "message": "No teeth found"}
228
+ health_res = predict_teeth_health(image_bytes)
229
+ if health_res["predicted_class"].lower() == "good teeth" and health_res["confidence"] >= 0.84:
230
+ disease_res = {"message": "Healthy", "predicted_class": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  else:
232
+ disease_img = preprocess_for_disease(image_bytes)
233
+ disease_res = predict_disease(disease_img)
 
234
  return {
235
  "status": "success",
236
+ "binary_result": binary_res,
237
+ "teeth_health_result": health_res,
238
+ "disease_result": disease_res
239
  }
240
 
241
  # ============================================================
242
+ # ENDPOINTS
243
  # ============================================================
244
  @app.get("/")
245
  def root():
246
+ return {"system": "Teeth Detection API", "status": "running"}
 
 
 
247
 
248
  @app.post("/predict")
249
  async def predict(file: UploadFile = File(...), threshold: float = 0.5):
250
+ return teeth_diagnosis_pipeline(await file.read(), threshold)
 
 
 
251
 
252
  @app.post("/detect-teeth")
253
  async def detect_teeth(file: UploadFile = File(...)):
254
+ img = preprocess_for_binary(await file.read())
255
+ return predict_teeth(img)
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
  @app.post("/check-teeth-health")
258
  async def check_teeth_health(file: UploadFile = File(...)):
259
+ return predict_teeth_health(await file.read())
 
260
 
261
  @app.post("/advanced-recommendations")
262
  async def advanced_recommendations(
263
  file: UploadFile = File(...),
264
+ threshold: float = 0.5,
265
+ age: int = 18,
266
+ pain_level: int = 0,
267
+ bleeding: bool = False
268
  ):
269
+ diag = teeth_diagnosis_pipeline(await file.read(), threshold)
270
+ if diag["status"] != "success":
271
+ raise HTTPException(422, "Diagnosis failed")
272
+ top = diag["disease_result"].get("top_predictions", [])
273
+ if not top:
274
+ return {"status": "no_disease"}
275
+ recs = get_weighted_recommendations(top, age, pain_level, bleeding)
276
+ return recs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  # ============================================================
279
  # SERVER START
280
  # ============================================================
281
  if __name__ == "__main__":
282
  print("=" * 70)
283
+ print("Server starting at http://localhost:7860")
 
 
284
  print("=" * 70)
285
  uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt CHANGED
@@ -1,12 +1,9 @@
1
- tensorflow==2.15.0
2
- keras==2.15.0
3
- protobuf==3.20.3
4
- h5py==3.10.0
5
  fastapi==0.104.1
6
  uvicorn==0.24.0
7
  transformers==4.35.0
8
  torch==2.1.0
9
- torchvision==0.16.0
10
  Pillow==10.1.0
11
  numpy==1.23.5
12
  gdown==5.1.0
 
1
+ tensorflow==2.12.0
2
+ keras==2.12.0
 
 
3
  fastapi==0.104.1
4
  uvicorn==0.24.0
5
  transformers==4.35.0
6
  torch==2.1.0
 
7
  Pillow==10.1.0
8
  numpy==1.23.5
9
  gdown==5.1.0