vscode commited on
Commit
f1620d5
·
verified ·
1 Parent(s): 38cdfad

Upload 19 files

Browse files
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.pth filter=lfs diff=lfs merge=lfs -text
2
+ static/uploads/COVID19(465).jpg filter=lfs diff=lfs merge=lfs -text
3
+ static/uploads/Tuberculosis-664.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.env
4
+ .env
5
+ instance/
6
+ *.sqlite3
7
+ *.db
8
+ .DS_Store
9
+ venv/
10
+ .env.*
11
+ *.log
12
+ *.bak
13
+ .idea/
14
+ *.swp
15
+ static/uploads/
README.md CHANGED
@@ -1,10 +1 @@
1
- ---
2
- title: Lumina
3
- emoji: 🏃
4
- colorFrom: indigo
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # LuminaCXR
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from flask import Flask, render_template, request
4
+ from PIL import Image
5
+ import numpy as np
6
+ import cv2
7
+
8
+ from gradcam import GradCAM, model, classes
9
+ from torchvision import transforms
10
+
11
+ app = Flask(__name__)
12
+ UPLOAD_FOLDER = "static/uploads"
13
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
14
+
15
+ transform = transforms.Compose([
16
+ transforms.Resize((224, 224)),
17
+ transforms.ToTensor(),
18
+ transforms.Normalize([0.485, 0.456, 0.406],
19
+ [0.229, 0.224, 0.225])
20
+ ])
21
+
22
+ @app.route('/')
23
+ def index():
24
+ return render_template('index.html')
25
+
26
+ @app.route('/predict', methods=['POST'])
27
+ def predict():
28
+ if 'image' not in request.files:
29
+ return "No image uploaded", 400
30
+
31
+ file = request.files['image']
32
+ if file.filename == '':
33
+ return "No selected image", 400
34
+
35
+ img_path = os.path.join(UPLOAD_FOLDER, file.filename)
36
+ file.save(img_path)
37
+
38
+ image = Image.open(img_path).convert("RGB")
39
+ input_tensor = transform(image).unsqueeze(0).to(next(model.parameters()).device)
40
+
41
+ # Predict
42
+ with torch.no_grad():
43
+ output = model(input_tensor)
44
+ pred_idx = torch.argmax(output, dim=1).item()
45
+ confidence = torch.softmax(output, dim=1)[0][pred_idx].item()
46
+
47
+ # Grad-CAM
48
+ gradcam = GradCAM(model, model.features.denseblock4)
49
+ cam = gradcam.generate(input_tensor, class_idx=pred_idx)
50
+
51
+ # Prepare overlay
52
+ image_np = np.array(image.resize((224, 224)))
53
+ heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)
54
+ overlay = cv2.addWeighted(image_np, 0.6, heatmap, 0.4, 0)
55
+ cam_path = os.path.join(UPLOAD_FOLDER, "cam_" + file.filename)
56
+ cv2.imwrite(cam_path, cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR))
57
+
58
+ return render_template(
59
+ 'result.html',
60
+ prediction=classes[pred_idx],
61
+ confidence=f"{confidence * 100:.2f}%",
62
+ uploaded_image=file.filename,
63
+ cam_image="cam_" + file.filename
64
+ )
65
+
66
+ if __name__ == '__main__':
67
+ import os
68
+ port = int(os.environ.get("PORT", 5000))
69
+ app.run(debug=False, host='0.0.0.0', port=port)
gradcam.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ import cv2
4
+ from torchvision import models
5
+ from PIL import Image
6
+
7
+ # ----------------------------- Device -----------------------------
8
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
9
+
10
+ # ----------------------------- Classes -----------------------------
11
+ classes = ['COVID19', 'NORMAL', 'PNEUMONIA', 'TUBERCULOSIS']
12
+
13
+ # ----------------------------- Custom DenseNet Forward -----------------------------
14
+ from torchvision.models.densenet import DenseNet
15
+
16
+ class CustomDenseNet(DenseNet):
17
+ def forward(self, x):
18
+ features = self.features(x)
19
+ out = torch.relu(features.clone()) # clone avoids inplace operation
20
+ out = torch.nn.functional.adaptive_avg_pool2d(out, (1, 1)).view(x.size(0), -1)
21
+ out = self.classifier(out)
22
+ return out
23
+
24
+ # ----------------------------- Load Model -----------------------------
25
+ def load_model():
26
+ model = CustomDenseNet(
27
+ growth_rate=32, block_config=(6, 12, 24, 16),
28
+ num_init_features=64, bn_size=4, drop_rate=0,
29
+ num_classes=len(classes)
30
+ )
31
+ model.load_state_dict(torch.load("model.pth", map_location=device))
32
+ model.to(device)
33
+ model.eval()
34
+ return model
35
+
36
+ model = load_model()
37
+
38
+ # ----------------------------- Grad-CAM Class -----------------------------
39
+ class GradCAM:
40
+ def __init__(self, model, target_layer):
41
+ self.model = model
42
+ self.target_layer = target_layer
43
+ self.gradients = None
44
+ self.activations = None
45
+
46
+ target_layer.register_forward_hook(self.save_activation)
47
+ target_layer.register_full_backward_hook(self.save_gradient)
48
+
49
+ def save_activation(self, module, input, output):
50
+ self.activations = output.detach()
51
+
52
+ def save_gradient(self, module, grad_input, grad_output):
53
+ self.gradients = grad_output[0].detach()
54
+
55
+ def generate(self, input_tensor, class_idx=None):
56
+ output = self.model(input_tensor)
57
+
58
+ if class_idx is None:
59
+ class_idx = torch.argmax(output)
60
+
61
+ self.model.zero_grad()
62
+ loss = output[:, class_idx]
63
+ loss.backward()
64
+
65
+ grads = self.gradients[0].cpu().numpy()
66
+ activations = self.activations[0].cpu().numpy()
67
+
68
+ weights = np.mean(grads, axis=(1, 2))
69
+ cam = np.zeros(activations.shape[1:], dtype=np.float32)
70
+ for i, w in enumerate(weights):
71
+ cam += w * activations[i]
72
+
73
+ cam = np.maximum(cam, 0)
74
+ cam = cv2.resize(cam, (224, 224))
75
+ cam -= np.min(cam)
76
+ cam /= np.max(cam) + 1e-8
77
+ return cam
model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c5ccade85c8f3b241c9e83c292ab340a2bb5e45b8cf280217dcc81bc65d4d371
3
+ size 133
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ torch==2.0.1
2
+ torchvision==0.15.2
3
+ flask==2.2.5
4
+ numpy==1.24.4
5
+ Pillow==9.5.0
6
+ opencv-python-headless==4.7.0.72
7
+ gunicorn
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.8.18
static/css/styles.css ADDED
@@ -0,0 +1,589 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Reset and Base Styles */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ body {
9
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13
+ min-height: 100vh;
14
+ }
15
+
16
+ .container {
17
+ max-width: 1200px;
18
+ margin: 0 auto;
19
+ padding: 20px;
20
+ }
21
+
22
+ /* Header Styles */
23
+ .header {
24
+ text-align: center;
25
+ margin-bottom: 40px;
26
+ color: white;
27
+ }
28
+
29
+ .header-icon {
30
+ font-size: 3rem;
31
+ margin-bottom: 20px;
32
+ opacity: 0.9;
33
+ }
34
+
35
+ .header-title {
36
+ font-size: 2.5rem;
37
+ font-weight: 700;
38
+ margin-bottom: 10px;
39
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
40
+ }
41
+
42
+ .header-subtitle {
43
+ font-size: 1.1rem;
44
+ opacity: 0.9;
45
+ font-weight: 300;
46
+ }
47
+
48
+ /* Main Content */
49
+ .main-content {
50
+ background: white;
51
+ border-radius: 20px;
52
+ padding: 40px;
53
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
54
+ margin-bottom: 20px;
55
+ }
56
+
57
+ /* Upload Card */
58
+ .upload-card {
59
+ text-align: center;
60
+ padding: 40px;
61
+ border: 2px dashed #e0e6ed;
62
+ border-radius: 15px;
63
+ background: #f8fafc;
64
+ transition: all 0.3s ease;
65
+ margin-bottom: 40px;
66
+ }
67
+
68
+ .upload-card:hover {
69
+ border-color: #667eea;
70
+ background: #f1f5f9;
71
+ }
72
+
73
+ .upload-icon {
74
+ font-size: 3rem;
75
+ color: #667eea;
76
+ margin-bottom: 20px;
77
+ }
78
+
79
+ .upload-form {
80
+ display: flex;
81
+ flex-direction: column;
82
+ align-items: center;
83
+ gap: 20px;
84
+ }
85
+
86
+ /* File Input Styles */
87
+ .file-input-wrapper {
88
+ position: relative;
89
+ width: 100%;
90
+ max-width: 400px;
91
+ }
92
+
93
+ .file-input {
94
+ position: absolute;
95
+ opacity: 0;
96
+ width: 100%;
97
+ height: 100%;
98
+ cursor: pointer;
99
+ }
100
+
101
+ .file-label {
102
+ display: block;
103
+ padding: 20px;
104
+ background: #667eea;
105
+ color: white;
106
+ border-radius: 10px;
107
+ cursor: pointer;
108
+ transition: all 0.3s ease;
109
+ text-align: center;
110
+ }
111
+
112
+ .file-label:hover {
113
+ background: #5a67d8;
114
+ transform: translateY(-2px);
115
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
116
+ }
117
+
118
+ .file-label-text {
119
+ display: block;
120
+ font-size: 1.1rem;
121
+ font-weight: 600;
122
+ margin-bottom: 5px;
123
+ }
124
+
125
+ .file-label-subtext {
126
+ font-size: 0.9rem;
127
+ opacity: 0.8;
128
+ }
129
+
130
+ /* Preview Styles */
131
+ .preview-container {
132
+ text-align: center;
133
+ padding: 20px;
134
+ background: white;
135
+ border-radius: 10px;
136
+ border: 1px solid #e0e6ed;
137
+ }
138
+
139
+ .preview-image {
140
+ max-width: 200px;
141
+ max-height: 200px;
142
+ border-radius: 8px;
143
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
144
+ }
145
+
146
+ .preview-text {
147
+ margin-top: 10px;
148
+ color: #667eea;
149
+ font-weight: 500;
150
+ }
151
+
152
+ /* Submit Button */
153
+ .submit-btn {
154
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
155
+ color: white;
156
+ border: none;
157
+ padding: 15px 30px;
158
+ font-size: 1.1rem;
159
+ font-weight: 600;
160
+ border-radius: 10px;
161
+ cursor: pointer;
162
+ transition: all 0.3s ease;
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 10px;
166
+ }
167
+
168
+ .submit-btn:hover {
169
+ transform: translateY(-2px);
170
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
171
+ }
172
+
173
+ /* Info Section */
174
+ .info-section {
175
+ text-align: center;
176
+ margin-top: 40px;
177
+ }
178
+
179
+ .info-section h3 {
180
+ font-size: 1.5rem;
181
+ margin-bottom: 30px;
182
+ color: #2d3748;
183
+ }
184
+
185
+ .info-grid {
186
+ display: grid;
187
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
188
+ gap: 30px;
189
+ margin-top: 30px;
190
+ }
191
+
192
+ .info-item {
193
+ text-align: center;
194
+ padding: 20px;
195
+ }
196
+
197
+ .info-item i {
198
+ font-size: 2rem;
199
+ color: #667eea;
200
+ margin-bottom: 15px;
201
+ }
202
+
203
+ .info-item h4 {
204
+ font-size: 1.2rem;
205
+ margin-bottom: 10px;
206
+ color: #2d3748;
207
+ }
208
+
209
+ .info-item p {
210
+ color: #718096;
211
+ }
212
+
213
+ /* Results Page Styles */
214
+ .results-summary {
215
+ display: grid;
216
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
217
+ gap: 30px;
218
+ margin-bottom: 40px;
219
+ }
220
+
221
+ .result-card {
222
+ background: white;
223
+ padding: 30px;
224
+ border-radius: 15px;
225
+ text-align: center;
226
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
227
+ border: 1px solid #e0e6ed;
228
+ transition: transform 0.3s ease;
229
+ }
230
+
231
+ .result-card:hover {
232
+ transform: translateY(-5px);
233
+ }
234
+
235
+ .result-icon {
236
+ font-size: 2.5rem;
237
+ margin-bottom: 15px;
238
+ }
239
+
240
+ .prediction-card .result-icon {
241
+ color: #48bb78;
242
+ }
243
+
244
+ .confidence-card .result-icon {
245
+ color: #667eea;
246
+ }
247
+
248
+ .result-card h3 {
249
+ font-size: 1.3rem;
250
+ margin-bottom: 15px;
251
+ color: #2d3748;
252
+ }
253
+
254
+ .prediction-value {
255
+ font-size: 1.5rem;
256
+ font-weight: 700;
257
+ color: #48bb78;
258
+ text-transform: capitalize;
259
+ }
260
+
261
+ .confidence-value {
262
+ font-size: 1.5rem;
263
+ font-weight: 700;
264
+ color: #667eea;
265
+ margin-bottom: 15px;
266
+ }
267
+
268
+ .confidence-bar {
269
+ width: 100%;
270
+ height: 8px;
271
+ background: #e0e6ed;
272
+ border-radius: 4px;
273
+ overflow: hidden;
274
+ }
275
+
276
+ .confidence-fill {
277
+ height: 100%;
278
+ background: linear-gradient(90deg, #667eea, #48bb78);
279
+ border-radius: 4px;
280
+ transition: width 1s ease;
281
+ }
282
+
283
+ /* Comparison Section */
284
+ .comparison-section {
285
+ margin-top: 40px;
286
+ }
287
+
288
+ .section-title {
289
+ font-size: 1.8rem;
290
+ text-align: center;
291
+ margin-bottom: 30px;
292
+ color: #2d3748;
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ gap: 10px;
297
+ }
298
+
299
+ .image-comparison {
300
+ display: grid;
301
+ grid-template-columns: 1fr auto 1fr;
302
+ gap: 30px;
303
+ align-items: center;
304
+ margin-bottom: 30px;
305
+ }
306
+
307
+ .image-container {
308
+ text-align: center;
309
+ }
310
+
311
+ .image-header {
312
+ margin-bottom: 20px;
313
+ }
314
+
315
+ .image-header h3 {
316
+ font-size: 1.3rem;
317
+ color: #2d3748;
318
+ margin-bottom: 5px;
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ gap: 8px;
323
+ }
324
+
325
+ .image-header p {
326
+ color: #718096;
327
+ font-size: 0.9rem;
328
+ }
329
+
330
+ .image-wrapper {
331
+ position: relative;
332
+ display: inline-block;
333
+ border-radius: 15px;
334
+ overflow: hidden;
335
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
336
+ transition: transform 0.3s ease;
337
+ }
338
+
339
+ .image-wrapper:hover {
340
+ transform: scale(1.02);
341
+ }
342
+
343
+ .comparison-image {
344
+ width: 100%;
345
+ max-width: 300px;
346
+ height: auto;
347
+ display: block;
348
+ }
349
+
350
+ .image-overlay {
351
+ position: absolute;
352
+ top: 10px;
353
+ left: 10px;
354
+ background: rgba(0, 0, 0, 0.7);
355
+ color: white;
356
+ padding: 5px 10px;
357
+ border-radius: 5px;
358
+ font-size: 0.8rem;
359
+ font-weight: 600;
360
+ }
361
+
362
+ .comparison-divider {
363
+ display: flex;
364
+ flex-direction: column;
365
+ align-items: center;
366
+ gap: 10px;
367
+ }
368
+
369
+ .divider-line {
370
+ width: 2px;
371
+ height: 40px;
372
+ background: #e0e6ed;
373
+ }
374
+
375
+ .divider-icon {
376
+ background: #667eea;
377
+ color: white;
378
+ width: 40px;
379
+ height: 40px;
380
+ border-radius: 50%;
381
+ display: flex;
382
+ align-items: center;
383
+ justify-content: center;
384
+ font-size: 1.2rem;
385
+ }
386
+
387
+ /* Explanation Card */
388
+ .explanation-card {
389
+ background: #f8fafc;
390
+ padding: 30px;
391
+ border-radius: 15px;
392
+ border-left: 4px solid #667eea;
393
+ margin-bottom: 30px;
394
+ }
395
+
396
+ .explanation-card h3 {
397
+ font-size: 1.3rem;
398
+ color: #2d3748;
399
+ margin-bottom: 15px;
400
+ display: flex;
401
+ align-items: center;
402
+ gap: 10px;
403
+ }
404
+
405
+ .explanation-card p {
406
+ color: #4a5568;
407
+ line-height: 1.7;
408
+ margin-bottom: 20px;
409
+ }
410
+
411
+ .heatmap-legend {
412
+ display: flex;
413
+ align-items: center;
414
+ gap: 15px;
415
+ flex-wrap: wrap;
416
+ }
417
+
418
+ .legend-label {
419
+ font-weight: 600;
420
+ color: #2d3748;
421
+ }
422
+
423
+ .legend-bar {
424
+ display: flex;
425
+ align-items: center;
426
+ gap: 10px;
427
+ flex: 1;
428
+ min-width: 200px;
429
+ }
430
+
431
+ .legend-gradient {
432
+ height: 20px;
433
+ width: 100px;
434
+ background: linear-gradient(90deg, #3182ce, #38a169, #d69e2e, #e53e3e);
435
+ border-radius: 10px;
436
+ border: 1px solid #e0e6ed;
437
+ }
438
+
439
+ .legend-low,
440
+ .legend-high {
441
+ font-size: 0.9rem;
442
+ color: #718096;
443
+ font-weight: 500;
444
+ }
445
+
446
+ /* Action Buttons */
447
+ .action-buttons {
448
+ display: flex;
449
+ justify-content: center;
450
+ gap: 20px;
451
+ flex-wrap: wrap;
452
+ margin-top: 40px;
453
+ }
454
+
455
+ .btn {
456
+ padding: 12px 24px;
457
+ border-radius: 10px;
458
+ text-decoration: none;
459
+ font-weight: 600;
460
+ border: none;
461
+ cursor: pointer;
462
+ transition: all 0.3s ease;
463
+ display: flex;
464
+ align-items: center;
465
+ gap: 8px;
466
+ font-size: 1rem;
467
+ }
468
+
469
+ .btn-primary {
470
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
471
+ color: white;
472
+ }
473
+
474
+ .btn-primary:hover {
475
+ transform: translateY(-2px);
476
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
477
+ }
478
+
479
+ .btn-secondary {
480
+ background: white;
481
+ color: #667eea;
482
+ border: 2px solid #667eea;
483
+ }
484
+
485
+ .btn-secondary:hover {
486
+ background: #667eea;
487
+ color: white;
488
+ transform: translateY(-2px);
489
+ }
490
+
491
+ /* Animations */
492
+ @keyframes fadeIn {
493
+ from {
494
+ opacity: 0;
495
+ transform: translateY(20px);
496
+ }
497
+ to {
498
+ opacity: 1;
499
+ transform: translateY(0);
500
+ }
501
+ }
502
+
503
+ .fade-in {
504
+ animation: fadeIn 0.6s ease forwards;
505
+ }
506
+
507
+ /* Responsive Design */
508
+ @media (max-width: 768px) {
509
+ .container {
510
+ padding: 10px;
511
+ }
512
+
513
+ .main-content {
514
+ padding: 20px;
515
+ }
516
+
517
+ .header-title {
518
+ font-size: 2rem;
519
+ }
520
+
521
+ .image-comparison {
522
+ grid-template-columns: 1fr;
523
+ gap: 20px;
524
+ }
525
+
526
+ .comparison-divider {
527
+ flex-direction: row;
528
+ justify-content: center;
529
+ }
530
+
531
+ .divider-line {
532
+ width: 40px;
533
+ height: 2px;
534
+ }
535
+
536
+ .action-buttons {
537
+ flex-direction: column;
538
+ align-items: center;
539
+ }
540
+
541
+ .btn {
542
+ width: 100%;
543
+ max-width: 250px;
544
+ justify-content: center;
545
+ }
546
+
547
+ .heatmap-legend {
548
+ flex-direction: column;
549
+ align-items: flex-start;
550
+ }
551
+
552
+ .legend-bar {
553
+ width: 100%;
554
+ }
555
+ }
556
+
557
+ @media (max-width: 480px) {
558
+ .header-title {
559
+ font-size: 1.5rem;
560
+ }
561
+
562
+ .upload-card {
563
+ padding: 20px;
564
+ }
565
+
566
+ .results-summary {
567
+ grid-template-columns: 1fr;
568
+ }
569
+
570
+ .comparison-image {
571
+ max-width: 250px;
572
+ }
573
+ }
574
+
575
+ /* Print Styles */
576
+ @media print {
577
+ body {
578
+ background: white;
579
+ }
580
+
581
+ .action-buttons {
582
+ display: none;
583
+ }
584
+
585
+ .main-content {
586
+ box-shadow: none;
587
+ border: 1px solid #e0e6ed;
588
+ }
589
+ }
static/uploads/COVID19(465).jpg ADDED

Git LFS Details

  • SHA256: 6732de53c41f29096398c07e50f165414db7c9e73bb972f9420a5d97cc962050
  • Pointer size: 131 Bytes
  • Size of remote file: 868 kB
static/uploads/Tuberculosis-664.png ADDED

Git LFS Details

  • SHA256: 88adae2e5f5994481be3b47160b0355eeb6d951515b932758f80242f62eb2be8
  • Pointer size: 131 Bytes
  • Size of remote file: 220 kB
static/uploads/Tuberculosis-665.png ADDED
static/uploads/Tuberculosis-666.png ADDED
static/uploads/cam_COVID19(465).jpg ADDED
static/uploads/cam_Tuberculosis-664.png ADDED
static/uploads/cam_Tuberculosis-665.png ADDED
static/uploads/cam_Tuberculosis-666.png ADDED
templates/index.html ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Chest X-ray AI Classifier</title>
7
+
8
+ <!-- Corrected CSS path -->
9
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/styles.css') }}">
10
+
11
+ <!-- Font Awesome for icons -->
12
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
13
+ </head>
14
+ <body>
15
+ <div class="container">
16
+ <header class="header">
17
+ <div class="header-icon">
18
+ <i class="fas fa-x-ray"></i>
19
+ </div>
20
+ <h1 class="header-title">Chest X-ray AI Classifier</h1>
21
+ <p class="header-subtitle">Upload your chest X-ray image for AI-powered analysis</p>
22
+ </header>
23
+
24
+ <main class="main-content">
25
+ <div class="upload-card">
26
+ <div class="upload-icon">
27
+ <i class="fas fa-cloud-upload-alt"></i>
28
+ </div>
29
+
30
+ <form method="POST" action="/predict" enctype="multipart/form-data" class="upload-form">
31
+ <div class="file-input-wrapper">
32
+ <input type="file" name="image" id="image" accept="image/*" required class="file-input">
33
+ <label for="image" class="file-label">
34
+ <span class="file-label-text">Choose X-ray Image</span>
35
+ <span class="file-label-subtext">PNG, JPG, JPEG up to 10MB</span>
36
+ </label>
37
+ </div>
38
+
39
+ <div class="preview-container" id="preview-container" style="display: none;">
40
+ <img id="preview-image" src="/static/img/placeholder.svg" alt="Preview" class="preview-image">
41
+ <p class="preview-text">Image ready for analysis</p>
42
+ </div>
43
+
44
+ <button type="submit" class="submit-btn">
45
+ <i class="fas fa-brain"></i>
46
+ Analyze X-ray
47
+ </button>
48
+ </form>
49
+ </div>
50
+
51
+ <div class="info-section">
52
+ <h3>How it works</h3>
53
+ <div class="info-grid">
54
+ <div class="info-item">
55
+ <i class="fas fa-upload"></i>
56
+ <h4>Upload</h4>
57
+ <p>Select your chest X-ray image</p>
58
+ </div>
59
+ <div class="info-item">
60
+ <i class="fas fa-cogs"></i>
61
+ <h4>Analyze</h4>
62
+ <p>AI processes the image</p>
63
+ </div>
64
+ <div class="info-item">
65
+ <i class="fas fa-chart-line"></i>
66
+ <h4>Results</h4>
67
+ <p>Get classification and visualization</p>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </main>
72
+ </div>
73
+
74
+ <script>
75
+ const fileInput = document.getElementById('image');
76
+ const previewContainer = document.getElementById('preview-container');
77
+ const previewImage = document.getElementById('preview-image');
78
+ const fileLabel = document.querySelector('.file-label-text');
79
+
80
+ fileInput.addEventListener('change', function(e) {
81
+ const file = e.target.files[0];
82
+ if (file) {
83
+ const reader = new FileReader();
84
+ reader.onload = function(e) {
85
+ previewImage.src = e.target.result;
86
+ previewContainer.style.display = 'block';
87
+ fileLabel.textContent = file.name;
88
+ };
89
+ reader.readAsDataURL(file);
90
+ }
91
+ });
92
+ </script>
93
+ </body>
94
+ </html>
templates/result.html ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Analysis Results - Chest X-ray AI Classifier</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <header class="header">
13
+ <div class="header-icon">
14
+ <i class="fas fa-chart-line"></i>
15
+ </div>
16
+ <h1 class="header-title">Analysis Results</h1>
17
+ <p class="header-subtitle">AI-powered chest X-ray classification results</p>
18
+ </header>
19
+
20
+ <main class="main-content">
21
+ <!-- Results Summary -->
22
+ <div class="results-summary">
23
+ <div class="result-card prediction-card">
24
+ <div class="result-icon">
25
+ <i class="fas fa-diagnoses"></i>
26
+ </div>
27
+ <h3>Classification</h3>
28
+ <p class="prediction-value">{{ prediction }}</p>
29
+ </div>
30
+
31
+ <div class="result-card confidence-card">
32
+ <div class="result-icon">
33
+ <i class="fas fa-percentage"></i>
34
+ </div>
35
+ <h3>Confidence</h3>
36
+ <p class="confidence-value">{{ confidence }}</p>
37
+ <div class="confidence-bar">
38
+ <!-- Optional: visualize bar if desired -->
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Image Comparison Section -->
44
+ <div class="comparison-section">
45
+ <h2 class="section-title">
46
+ <i class="fas fa-images"></i>
47
+ Image Analysis Comparison
48
+ </h2>
49
+
50
+ <div class="image-comparison">
51
+ <div class="image-container">
52
+ <div class="image-header">
53
+ <h3>
54
+ <i class="fas fa-file-medical"></i>
55
+ Original X-ray
56
+ </h3>
57
+ <p>Uploaded chest X-ray image</p>
58
+ </div>
59
+ <div class="image-wrapper">
60
+ <img src="{{ url_for('static', filename='uploads/' + uploaded_image) }}" alt="Original X-ray" class="comparison-image">
61
+ <div class="image-overlay">
62
+ <span class="image-label">Original</span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="comparison-divider">
68
+ <div class="divider-line"></div>
69
+ <div class="divider-icon">
70
+ <i class="fas fa-arrows-alt-h"></i>
71
+ </div>
72
+ <div class="divider-line"></div>
73
+ </div>
74
+
75
+ <div class="image-container">
76
+ <div class="image-header">
77
+ <h3>
78
+ <i class="fas fa-eye"></i>
79
+ Grad-CAM Visualization
80
+ </h3>
81
+ <p>AI attention heatmap overlay</p>
82
+ </div>
83
+ <div class="image-wrapper">
84
+ <img src="{{ url_for('static', filename='uploads/' + cam_image) }}" alt="Grad-CAM Visualization" class="comparison-image">
85
+ <div class="image-overlay">
86
+ <span class="image-label">Grad-CAM</span>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Explanation Section -->
93
+ <div class="explanation-card">
94
+ <h3>
95
+ <i class="fas fa-info-circle"></i>
96
+ Understanding Grad-CAM
97
+ </h3>
98
+ <p>
99
+ Grad-CAM (Gradient-weighted Class Activation Mapping) highlights the regions
100
+ in the X-ray that the AI model focused on when making its prediction.
101
+ Warmer colors (red/yellow) indicate areas of higher importance for the classification.
102
+ </p>
103
+ <div class="heatmap-legend">
104
+ <span class="legend-label">Attention Level:</span>
105
+ <div class="legend-bar">
106
+ <span class="legend-low">Low</span>
107
+ <div class="legend-gradient"></div>
108
+ <span class="legend-high">High</span>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <!-- Action Buttons -->
115
+ <div class="action-buttons">
116
+ <a href="{{ url_for('index') }}" class="btn btn-primary">
117
+ <i class="fas fa-plus"></i>
118
+ Analyze Another Image
119
+ </a>
120
+ <button class="btn btn-secondary" onclick="window.print()">
121
+ <i class="fas fa-print"></i>
122
+ Print Results
123
+ </button>
124
+ <button class="btn btn-secondary" onclick="downloadResults()">
125
+ <i class="fas fa-download"></i>
126
+ Download Report
127
+ </button>
128
+ </div>
129
+ </main>
130
+ </div>
131
+
132
+ <script>
133
+ function downloadResults() {
134
+ alert('Download functionality would be implemented here');
135
+ }
136
+
137
+ document.addEventListener('DOMContentLoaded', function() {
138
+ const elements = document.querySelectorAll('.result-card, .image-container');
139
+ elements.forEach((el, index) => {
140
+ el.style.animationDelay = `${index * 0.1}s`;
141
+ el.classList.add('fade-in');
142
+ });
143
+ });
144
+ </script>
145
+ </body>
146
+ </html>