Mikecode123 commited on
Commit
879d055
·
verified ·
1 Parent(s): 498f27f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -147
app.py CHANGED
@@ -1,208 +1,153 @@
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())
 
 
 
 
1
  import torch
2
  import torch.nn as nn
3
  import torchvision.models as models
 
4
  from fastapi import FastAPI, UploadFile, File
5
  from PIL import Image
6
+ import io
7
+ import torchvision.transforms as transforms
8
+ import numpy as np
9
+ import tensorflow as tf
10
 
11
+ app = FastAPI(title="Alzheimer Ensemble API")
12
 
13
+ DEVICE = torch.device("cpu")
14
 
15
  # =========================
16
+ # LABELS
17
  # =========================
18
+ LABELS = [
19
+ "Mild Demented",
20
+ "Moderate Demented",
21
+ "Non Demented",
22
+ "Very Mild Demented"
23
+ ]
24
 
25
  # =========================
26
+ # TRANSFORM
27
  # =========================
28
+ transform = transforms.Compose([
29
+ transforms.Resize((224, 224)),
30
+ transforms.ToTensor(),
31
+ transforms.Normalize([0.5]*3, [0.5]*3)
32
+ ])
 
 
 
 
 
33
 
34
  # =========================
35
+ # SAFE KERAS LOADER
36
  # =========================
37
+ def load_keras_model(path):
38
+ try:
39
+ return tf.keras.models.load_model(path, compile=False)
40
+ except Exception as e:
41
+ print("Keras load failed:", path, e)
42
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  # =========================
45
+ # LOAD MODELS
46
  # =========================
47
+ model_121 = load_keras_model("densenet121_parkinsonsDATSCAN.keras")
48
+ model_169 = load_keras_model("parkinsons_densenet169DATSCAN.keras")
49
+ model_201 = load_keras_model("parkinsons_densenet201DATSCAN.keras")
 
 
 
 
50
 
51
+ models_list = [model_121, model_169, model_201]
52
 
53
  # =========================
54
+ # SINGLE PREDICTION (KERAS SAFE)
55
  # =========================
56
+ def predict_single(model, image_tensor):
57
+ if model is None:
58
+ return np.zeros(len(LABELS))
 
 
 
 
 
 
 
59
 
60
+ img = image_tensor.permute(0, 2, 3, 1).numpy()
61
 
62
+ preds = model.predict(img, verbose=0)[0]
63
+ return preds
 
 
 
64
 
 
 
 
 
 
 
 
 
 
65
  # =========================
66
+ # ENSEMBLE PREDICTION
 
 
 
 
 
 
67
  # =========================
68
  def ensemble_predict(image_bytes):
69
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
70
+ image = transform(image).unsqueeze(0)
71
 
72
+ preds_all = []
73
+ model_confidence_report = []
74
 
75
+ for i, m in enumerate(models_list):
76
+ preds = predict_single(m, image)
77
+ preds_all.append(preds)
 
78
 
79
+ model_confidence_report.append({
80
+ "model": f"model_{i+1}",
81
+ "confidence": float(np.max(preds)),
82
+ "prediction": LABELS[int(np.argmax(preds))]
83
+ })
84
 
85
+ avg = np.mean(preds_all, axis=0)
86
 
87
  cls = int(np.argmax(avg))
88
+ conf = float(np.max(avg))
89
 
90
  return {
91
  "prediction": LABELS[cls],
92
  "class_id": cls,
93
+ "confidence": round(conf * 100, 2),
 
 
 
 
 
 
 
 
94
  "probabilities": {
95
  LABELS[i]: round(float(avg[i]) * 100, 2)
96
+ for i in range(len(LABELS))
97
+ },
98
+ "model_breakdown": model_confidence_report
99
  }
100
 
 
101
  # =========================
102
+ # INDIVIDUAL ENDPOINTS
103
  # =========================
104
+ @app.post("/predict/121")
105
+ async def predict_121(file: UploadFile = File(...)):
106
+ img = await file.read()
107
+ return ensemble_predict_single(img, model_121)
108
 
109
+ @app.post("/predict/169")
110
+ async def predict_169(file: UploadFile = File(...)):
111
+ img = await file.read()
112
+ return ensemble_predict_single(img, model_169)
113
+
114
+ @app.post("/predict/201")
115
+ async def predict_201(file: UploadFile = File(...)):
116
+ img = await file.read()
117
+ return ensemble_predict_single(img, model_201)
118
+
119
+ # helper
120
+ def ensemble_predict_single(image_bytes, model):
121
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
122
+ image = transform(image).unsqueeze(0)
123
 
124
+ preds = predict_single(model, image)
125
+ cls = int(np.argmax(preds))
126
 
127
  return {
128
  "prediction": LABELS[cls],
129
+ "confidence": float(np.max(preds)),
 
130
  "probabilities": {
131
+ LABELS[i]: float(preds[i])
132
+ for i in range(len(LABELS))
133
  }
134
  }
135
 
 
136
  # =========================
137
+ # ENSEMBLE ENDPOINT
138
  # =========================
 
 
 
 
 
 
 
 
139
  @app.post("/predict")
140
  async def predict(file: UploadFile = File(...)):
141
  image_bytes = await file.read()
142
  return ensemble_predict(image_bytes)
143
 
144
+ # =========================
145
+ # HEALTH CHECK
146
+ # =========================
147
+ @app.get("/")
148
+ def home():
149
+ return {
150
+ "status": "running",
151
+ "models": ["121", "169", "201"],
152
+ "ensemble": True
153
+ }