Commit ·
3a1ed33
1
Parent(s): 14d1289
Deploy FastAPI teeth detection API
Browse files- .gitignore +0 -0
- Dockerfile +0 -0
- README.md +0 -11
- app.py +1325 -0
- knowledge_base/clinical_rules.json +331 -0
- 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
|
|
|