Mikecode123 commited on
Commit
e920189
·
verified ·
1 Parent(s): 87ab2cf

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +208 -0
app.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import torchvision.models as models
7
+ import tensorflow as tf
8
+ from fastapi import FastAPI, UploadFile, File
9
+ from PIL import Image
10
+ import cv2
11
+ import nibabel as nib
12
+
13
+ app = FastAPI(title="Parkinson + DaTscan AI API")
14
+
15
+ DEVICE = "cpu"
16
+
17
+ # =========================
18
+ # LABELS (BINARY CLASS)
19
+ # =========================
20
+ LABELS = ["No Parkinson's", "Parkinson's Disease"]
21
+
22
+ # =========================
23
+ # LOAD KERAS MODELS
24
+ # =========================
25
+ def load_keras(path):
26
+ return tf.keras.models.load_model(path, compile=False)
27
+
28
+ model_121 = load_keras("densenet121_parkinsonsDATSCAN.keras")
29
+ model_169 = load_keras("parkinsons_densenet169DATSCAN.keras")
30
+ model_201 = load_keras("parkinsons_densenet201DATSCAN.keras")
31
+
32
+ # optional fixed model (if better)
33
+ model_fixed = load_keras("densenet121_parkinsonsDATSCAN_fixed.keras")
34
+
35
+
36
+ # =========================
37
+ # 3D CNN MODEL (PyTorch)
38
+ # =========================
39
+ def build_3dcnn():
40
+ model = nn.Sequential(
41
+ nn.Conv3d(1, 32, 3, padding=1),
42
+ nn.ReLU(),
43
+ nn.MaxPool3d(2),
44
+
45
+ nn.Conv3d(32, 64, 3, padding=1),
46
+ nn.ReLU(),
47
+ nn.MaxPool3d(2),
48
+
49
+ nn.AdaptiveAvgPool3d((4, 4, 4)),
50
+ nn.Flatten(),
51
+ nn.Linear(64 * 4 * 4 * 4, 128),
52
+ nn.ReLU(),
53
+ nn.Linear(128, 2)
54
+ )
55
+ return model.to(DEVICE).eval()
56
+
57
+ model_3dcnn = build_3dcnn()
58
+
59
+
60
+ # =========================
61
+ # IMAGE PREPROCESSING (2D)
62
+ # =========================
63
+ def preprocess_2d(image_bytes):
64
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
65
+ image = np.array(image)
66
+ image = cv2.resize(image, (128, 128))
67
+ image = image / 255.0
68
+ image = np.expand_dims(image, axis=0)
69
+ return image
70
+
71
+
72
+ # =========================
73
+ # NIFTI PREPROCESSING (3D)
74
+ # =========================
75
+ def preprocess_3d(file_bytes):
76
+ temp_path = "temp.nii"
77
+ with open(temp_path, "wb") as f:
78
+ f.write(file_bytes)
79
+
80
+ volume = nib.load(temp_path).get_fdata()
81
+ volume = np.squeeze(volume)
82
+
83
+ if len(volume.shape) == 2:
84
+ volume = np.stack([volume] * 32, axis=-1)
85
+
86
+ depth = volume.shape[2]
87
+
88
+ slices = []
89
+ for i in np.linspace(0, depth - 1, 32).astype(int):
90
+ sl = volume[:, :, i]
91
+ sl = cv2.resize(sl, (64, 64))
92
+ slices.append(sl)
93
+
94
+ vol = np.stack(slices, axis=0)
95
+ vol = np.expand_dims(vol, axis=0)
96
+ vol = np.expand_dims(vol, axis=0)
97
+
98
+ return torch.tensor(vol, dtype=torch.float32).to(DEVICE)
99
+
100
+
101
+ # =========================
102
+ # KERAS SINGLE PREDICT
103
+ # =========================
104
+ def predict_keras(model, x):
105
+ pred = model.predict(x, verbose=0)[0]
106
+ return pred
107
+
108
+
109
+ # =========================
110
+ # ENSEMBLE LOGIC (4 MODELS)
111
+ # =========================
112
+ def ensemble_predict(image_bytes):
113
+
114
+ x = preprocess_2d(image_bytes)
115
+
116
+ p1 = predict_keras(model_121, x)
117
+ p2 = predict_keras(model_169, x)
118
+ p3 = predict_keras(model_201, x)
119
+ p4 = predict_keras(model_fixed, x)
120
+
121
+ preds = np.array([p1, p2, p3, p4])
122
+
123
+ avg = np.mean(preds, axis=0)
124
+
125
+ cls = int(np.argmax(avg))
126
+ confidence = float(avg[cls] * 100)
127
+
128
+ return {
129
+ "prediction": LABELS[cls],
130
+ "class_id": cls,
131
+ "confidence": round(confidence, 2),
132
+
133
+ "model_confidences": {
134
+ "DenseNet121": round(float(np.max(p1)) * 100, 2),
135
+ "DenseNet169": round(float(np.max(p2)) * 100, 2),
136
+ "DenseNet201": round(float(np.max(p3)) * 100, 2),
137
+ "Fixed121": round(float(np.max(p4)) * 100, 2),
138
+ },
139
+
140
+ "probabilities": {
141
+ LABELS[i]: round(float(avg[i]) * 100, 2)
142
+ for i in range(2)
143
+ }
144
+ }
145
+
146
+
147
+ # =========================
148
+ # 3D CNN PREDICTION
149
+ # =========================
150
+ def predict_3d(file_bytes):
151
+ x = preprocess_3d(file_bytes)
152
+
153
+ with torch.no_grad():
154
+ out = model_3dcnn(x)
155
+ probs = torch.softmax(out, dim=1)[0]
156
+
157
+ cls = int(torch.argmax(probs))
158
+ confidence = float(probs[cls] * 100)
159
+
160
+ return {
161
+ "prediction": LABELS[cls],
162
+ "class_id": cls,
163
+ "confidence": round(confidence, 2),
164
+ "probabilities": {
165
+ LABELS[i]: round(float(probs[i]) * 100, 2)
166
+ for i in range(2)
167
+ }
168
+ }
169
+
170
+
171
+ # =========================
172
+ # ROUTES
173
+ # =========================
174
+ @app.get("/")
175
+ def home():
176
+ return {
177
+ "status": "running",
178
+ "models": ["121", "169", "201", "fixed", "3dcnn"]
179
+ }
180
+
181
+
182
+ @app.post("/predict")
183
+ async def predict(file: UploadFile = File(...)):
184
+ image_bytes = await file.read()
185
+ return ensemble_predict(image_bytes)
186
+
187
+
188
+ @app.post("/predict/121")
189
+ async def p121(file: UploadFile = File(...)):
190
+ x = preprocess_2d(await file.read())
191
+ return {"model": "121", "prob": predict_keras(model_121, x).tolist()}
192
+
193
+
194
+ @app.post("/predict/169")
195
+ async def p169(file: UploadFile = File(...)):
196
+ x = preprocess_2d(await file.read())
197
+ return {"model": "169", "prob": predict_keras(model_169, x).tolist()}
198
+
199
+
200
+ @app.post("/predict/201")
201
+ async def p201(file: UploadFile = File(...)):
202
+ x = preprocess_2d(await file.read())
203
+ return {"model": "201", "prob": predict_keras(model_201, x).tolist()}
204
+
205
+
206
+ @app.post("/predict/3d")
207
+ async def p3d(file: UploadFile = File(...)):
208
+ return predict_3d(await file.read())