talha420 commited on
Commit
d4f7659
·
1 Parent(s): 4500636

deploy cardioguard

Browse files
README.md CHANGED
@@ -1,11 +1,10 @@
1
  ---
2
  title: CardioGuard AI
3
- emoji: 🦀
4
- colorFrom: pink
5
  colorTo: red
6
- sdk: docker
 
 
7
  pinned: false
8
- short_description: Here you can find the Multi-Classification for Heart Disease
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: CardioGuard AI
3
+ emoji: 🫀
4
+ colorFrom: blue
5
  colorTo: red
6
+ sdk: gradio
7
+ sdk_version: 4.44.0
8
+ app_file: app.py
9
  pinned: false
10
+ ---
 
 
 
app.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from PIL import Image
3
+ import numpy as np
4
+
5
+ from app.predictor import predict_disease
6
+ from app.gradcam import generate_gradcam
7
+ from app.predictor import compute_ai_risk
8
+
9
+ def predict(image):
10
+ # 1️⃣ Disease prediction
11
+ label, confidence, tensor, original = predict_disease(image)
12
+
13
+ # 2️⃣ GradCAM heatmap
14
+ heatmap = generate_gradcam(tensor, original)
15
+
16
+ # 3️⃣ Risk prediction
17
+ risk = compute_ai_risk(label, confidence, heatmap)
18
+
19
+ report = f"""
20
+ 🫀 AI Clinical Report
21
+
22
+ Disease: {label}
23
+ Confidence: {round(confidence*100,2)} %
24
+ Risk Level: {risk}
25
+ """
26
+
27
+ return heatmap, report
28
+
29
+ demo = gr.Interface(
30
+ fn=predict,
31
+ inputs=gr.Image(type="pil"),
32
+ outputs=[gr.Image(), gr.Textbox()],
33
+ title="CardioGuard AI",
34
+ description="Upload Chest X-ray to detect heart disease"
35
+ )
36
+
37
+ demo.launch()
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (127 Bytes). View file
 
app/__pycache__/main.cpython-310.pyc ADDED
Binary file (1.48 kB). View file
 
app/__pycache__/model_loader.cpython-310.pyc ADDED
Binary file (1.59 kB). View file
 
app/__pycache__/predictor.cpython-310.pyc ADDED
Binary file (4.5 kB). View file
 
app/__pycache__/train_risk_model.cpython-310.pyc ADDED
Binary file (958 Bytes). View file
 
app/main.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ print("STEP 1: main.py starting import")
2
+
3
+ from fastapi import FastAPI, UploadFile, File
4
+ print("STEP 2: fastapi imported")
5
+
6
+ from app.model_loader import load_all_models
7
+ print("STEP 3: model_loader imported")
8
+
9
+ from app.predictor import run_full_pipeline
10
+ print("STEP 4: predictor imported")
11
+
12
+
13
+
14
+ from fastapi import FastAPI, UploadFile, File
15
+ import numpy as np
16
+ import cv2
17
+
18
+ from app.model_loader import load_all_models
19
+ from app.predictor import run_full_pipeline
20
+
21
+ app = FastAPI(title="CardioGuard AI API")
22
+
23
+ # 🔥 LOAD MODELS WHEN SERVER STARTS
24
+ @app.on_event("startup")
25
+ def startup_event():
26
+ load_all_models()
27
+
28
+
29
+ # 🩺 Health check route
30
+ @app.get("/")
31
+ def root():
32
+ return {"message": "CardioGuard API is running"}
33
+
34
+
35
+ # ❤️‍🔥 Prediction Route
36
+ @app.post("/predict")
37
+ async def predict_xray(file: UploadFile = File(...)):
38
+
39
+ image_bytes = await file.read()
40
+
41
+ # convert bytes → PIL image
42
+ from PIL import Image
43
+ import io
44
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
45
+
46
+ result = run_full_pipeline(image)
47
+
48
+ # convert heatmap image → base64 (so frontend can display)
49
+ import base64
50
+ _, buffer = cv2.imencode(".png", result["heatmap"])
51
+ heatmap_base64 = base64.b64encode(buffer).decode("utf-8")
52
+
53
+ return {
54
+ "disease": result["disease"],
55
+ "confidence": result["confidence"],
56
+ "risk": result["risk"],
57
+ "heatmap": heatmap_base64
58
+ }
app/model_loader.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torchvision import models
3
+ from safetensors.torch import load_file
4
+ from huggingface_hub import hf_hub_download
5
+ import joblib
6
+
7
+ # ---------------- GLOBAL MODELS ---------------- #
8
+ cnn_model = None
9
+ kmeans_model = None
10
+ scaler = None
11
+
12
+
13
+ class DenseNet121_CheXpert(torch.nn.Module):
14
+ def __init__(self, num_labels=14):
15
+ super().__init__()
16
+ self.densenet = models.densenet121(weights=None)
17
+ num_features = self.densenet.classifier.in_features
18
+ self.densenet.classifier = torch.nn.Linear(num_features, num_labels)
19
+
20
+ def forward(self, x):
21
+ return self.densenet(x)
22
+
23
+
24
+ # ---------------- LOAD FUNCTION ---------------- #
25
+
26
+ def load_all_models():
27
+ global cnn_model, kmeans_model, scaler
28
+
29
+ print("Downloading DenseNet...")
30
+
31
+ local_path = hf_hub_download(
32
+ repo_id="itsomk/chexpert-densenet121",
33
+ filename="pytorch_model.safetensors"
34
+ )
35
+
36
+ print("Loading CNN...")
37
+ state = load_file(local_path)
38
+
39
+ cnn_model = DenseNet121_CheXpert()
40
+ cnn_model.load_state_dict(state, strict=False)
41
+ cnn_model.eval()
42
+
43
+ print("Loading KMeans + Scaler...")
44
+
45
+ kmeans_model = joblib.load("models/risk_model.pkl")
46
+ scaler = joblib.load("models/risk_scaler.pkl")
47
+
48
+ print("ALL MODELS READY 🚀")
49
+
50
+
51
+ # ---------------- AUTO LOAD (IMPORTANT FIX) ---------------- #
52
+
app/predictor.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ import cv2
4
+ from PIL import Image
5
+ import torch.nn.functional as F
6
+ from torchvision import transforms
7
+
8
+ # ---------------- LOAD MODELS ---------------- #
9
+ import app.model_loader as model_loader
10
+
11
+
12
+ # ---------------- IMAGE PREPROCESS ---------------- #
13
+
14
+ preprocess = transforms.Compose([
15
+ transforms.Resize((224, 224)),
16
+ transforms.ToTensor(),
17
+ transforms.Normalize(
18
+ mean=[0.485, 0.456, 0.406],
19
+ std=[0.229, 0.224, 0.225]
20
+ )
21
+ ])
22
+
23
+ CARDIOMEGALY_INDEX = 2
24
+ PNEUMOTHORAX_INDEX = 9
25
+ THRESHOLD = 0.96
26
+
27
+
28
+ # ---------------- DISEASE PREDICTION ---------------- #
29
+
30
+ def predict_disease(image: Image.Image):
31
+ img = image.convert("RGB")
32
+ x = preprocess(img).unsqueeze(0)
33
+
34
+ with torch.no_grad():
35
+ logits = model_loader.cnn_model(x)
36
+ probs = torch.sigmoid(logits).squeeze()
37
+
38
+ cardiomegaly_score = probs[CARDIOMEGALY_INDEX].item()
39
+ pneumothorax_score = probs[PNEUMOTHORAX_INDEX].item()
40
+
41
+ if cardiomegaly_score < THRESHOLD and pneumothorax_score < THRESHOLD:
42
+ predicted_label = "No Finding"
43
+ confidence = max(1 - cardiomegaly_score, 1 - pneumothorax_score)
44
+ else:
45
+ if cardiomegaly_score > pneumothorax_score:
46
+ predicted_label = "Cardiomegaly"
47
+ confidence = cardiomegaly_score
48
+ else:
49
+ predicted_label = "Pneumothorax (Pleural Effusion)"
50
+ confidence = pneumothorax_score
51
+
52
+ return predicted_label, confidence, x, img
53
+
54
+
55
+ import torch
56
+ import torch.nn.functional as F
57
+ import numpy as np
58
+ import cv2
59
+ from PIL import Image
60
+ import app.model_loader as model_loader
61
+
62
+
63
+ # ---------------- GRAD-CAM CLASS ---------------- #
64
+
65
+ class GradCAM:
66
+ def __init__(self, model, target_layer):
67
+ self.model = model
68
+ self.gradients = None
69
+ self.activations = None
70
+
71
+ # safer hooks (avoid full_backward_hook crash)
72
+ target_layer.register_forward_hook(self.forward_hook)
73
+ target_layer.register_backward_hook(self.backward_hook)
74
+
75
+ def forward_hook(self, module, input, output):
76
+ self.activations = output.detach()
77
+
78
+ def backward_hook(self, module, grad_input, grad_output):
79
+ self.gradients = grad_output[0].detach()
80
+
81
+ def generate_cam(self, input_tensor, target_class):
82
+
83
+ input_tensor = input_tensor.clone().requires_grad_(True)
84
+
85
+ output = self.model(input_tensor)
86
+
87
+ self.model.zero_grad(set_to_none=True)
88
+
89
+ class_score = output[0, target_class]
90
+
91
+ class_score.backward(retain_graph=True)
92
+
93
+ gradients = self.gradients[0]
94
+ activations = self.activations[0]
95
+
96
+ weights = torch.mean(gradients, dim=(1, 2))
97
+
98
+ cam = torch.zeros(activations.shape[1:], dtype=torch.float32)
99
+
100
+ for i, w in enumerate(weights):
101
+ cam += w * activations[i]
102
+
103
+ cam = F.relu(cam)
104
+ cam -= cam.min()
105
+
106
+ if cam.max() != 0:
107
+ cam /= cam.max()
108
+
109
+ return cam.cpu().numpy()
110
+
111
+
112
+ # ---------------- HEATMAP GENERATION ---------------- #
113
+
114
+ def generate_heatmap(x, img, predicted_label):
115
+
116
+ target_layer = model_loader.cnn_model.densenet.features.norm5
117
+
118
+ gradcam = GradCAM(model_loader.cnn_model, target_layer)
119
+
120
+ if predicted_label == "Cardiomegaly":
121
+ target_class = 2
122
+ elif predicted_label == "Pneumothorax (Pleural Effusion)":
123
+ target_class = 9
124
+ else:
125
+ target_class = 2
126
+
127
+ cam = gradcam.generate_cam(x, target_class)
128
+
129
+ # resize CAM
130
+ heatmap = cv2.resize(cam, (img.size[0], img.size[1]))
131
+
132
+ heatmap = np.uint8(255 * heatmap)
133
+ heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
134
+
135
+ # convert image
136
+ img_cv = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
137
+
138
+ # overlay
139
+ overlay = cv2.addWeighted(img_cv, 0.6, heatmap, 0.4, 0)
140
+
141
+ return overlay, cam
142
+
143
+
144
+ # ---------------- CLINICAL RISK ENGINE ---------------- #
145
+
146
+ def clinical_risk_label(predicted_label, confidence, cam):
147
+ cam_score = float(np.mean(cam))
148
+
149
+ if predicted_label == "No Finding":
150
+ if confidence >= 0.70:
151
+ return "LOW RISK"
152
+ else:
153
+ return "MEDIUM RISK (Low confidence normal)"
154
+
155
+ if confidence >= 0.85 or cam_score >= 0.20 or (confidence >= 0.70 and cam_score >= 0.15):
156
+ return "HIGH RISK"
157
+
158
+ if 0.50 <= confidence < 0.85 or 0.10 <= cam_score < 0.20:
159
+ return "MEDIUM RISK"
160
+
161
+ return "LOW RISK (Early detection)"
162
+
163
+
164
+ def compute_ai_risk(confidence, cam, predicted_label):
165
+ cam_score = float(np.mean(cam))
166
+ disease_flag = 0 if predicted_label == "No Finding" else 1
167
+
168
+ patient_vector = np.array([[confidence, cam_score, disease_flag]], dtype=np.float32)
169
+
170
+ patient_vector = model_loader.scaler.transform(patient_vector)
171
+ cluster = model_loader.kmeans_model.predict(patient_vector)[0]
172
+
173
+ if cluster == 0:
174
+ return "LOW RISK 🟢"
175
+ elif cluster == 1:
176
+ return "MEDIUM RISK 🟡"
177
+ else:
178
+ return "HIGH RISK 🔴"
179
+
180
+
181
+ # ---------------- FULL PIPELINE ---------------- #
182
+
183
+ def run_full_pipeline(image: Image.Image):
184
+ predicted_label, confidence, x, img = predict_disease(image)
185
+ overlay, cam = generate_heatmap(x, img, predicted_label)
186
+ risk = clinical_risk_label(predicted_label, confidence, cam)
187
+
188
+ return {
189
+ "disease": predicted_label,
190
+ "confidence": float(confidence),
191
+ "risk": risk,
192
+ "heatmap": overlay
193
+ }
app/train_risk_model.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from sklearn.preprocessing import StandardScaler
3
+ from sklearn.cluster import KMeans
4
+ import joblib
5
+ import os
6
+
7
+ print("Training Risk Model...")
8
+
9
+ os.makedirs("models", exist_ok=True)
10
+
11
+ np.random.seed(42)
12
+ n_samples = 500
13
+
14
+ conf_population = np.random.uniform(0.4, 0.98, n_samples)
15
+ cam_population = np.random.uniform(0.05, 0.35, n_samples)
16
+ disease_population = np.random.randint(0, 2, n_samples)
17
+
18
+ training_vectors = np.column_stack([
19
+ conf_population,
20
+ cam_population,
21
+ disease_population
22
+ ]).astype(np.float32)
23
+
24
+ scaler = StandardScaler()
25
+ training_vectors = scaler.fit_transform(training_vectors).astype(np.float32)
26
+
27
+ kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
28
+ kmeans.fit(training_vectors)
29
+
30
+ joblib.dump(kmeans, "models/risk_model.pkl")
31
+ joblib.dump(scaler, "models/risk_scaler.pkl")
32
+
33
+ print("✅ Risk model saved successfully!")
gradcam.py ADDED
File without changes
models/risk_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:229952cd47d9b96973ba4aefc1c8ee7cf72b2454becf259e731fd23d121d7d2d
3
+ size 2807
models/risk_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7612911534660e3ac165e31533555286e8f60ce8756625c0c2f3f7cb45d5f546
3
+ size 671
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ torchvision
3
+ scikit-learn
4
+ joblib
5
+ numpy
6
+ pillow
7
+ opencv-python-headless
8
+ gradio
9
+ huggingface_hub
10
+ safetensors