|
a |
|
b/templates/index.html |
|
|
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>Medical X-Ray Classifier</title> |
|
|
7 |
<style> |
|
|
8 |
/* General Styles */ |
|
|
9 |
body { |
|
|
10 |
font-family: 'Arial', sans-serif; |
|
|
11 |
background: linear-gradient(to right, #e3f2fd, #ffffff); |
|
|
12 |
margin: 0; |
|
|
13 |
padding: 0; |
|
|
14 |
display: flex; |
|
|
15 |
justify-content: center; |
|
|
16 |
align-items: center; |
|
|
17 |
min-height: 100vh; |
|
|
18 |
} |
|
|
19 |
|
|
|
20 |
.container { |
|
|
21 |
width: 90%; |
|
|
22 |
max-width: 1200px; |
|
|
23 |
background: #fff; |
|
|
24 |
border-radius: 12px; |
|
|
25 |
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); |
|
|
26 |
display: flex; |
|
|
27 |
overflow: hidden; |
|
|
28 |
animation: fadeIn 1s ease-in-out; |
|
|
29 |
} |
|
|
30 |
|
|
|
31 |
@keyframes fadeIn { |
|
|
32 |
from { |
|
|
33 |
opacity: 0; |
|
|
34 |
transform: translateY(20px); |
|
|
35 |
} |
|
|
36 |
to { |
|
|
37 |
opacity: 1; |
|
|
38 |
transform: translateY(0); |
|
|
39 |
} |
|
|
40 |
} |
|
|
41 |
|
|
|
42 |
.left-column { |
|
|
43 |
width: 35%; |
|
|
44 |
background: #f8f9fa; |
|
|
45 |
padding: 20px; |
|
|
46 |
border-right: 1px solid #ddd; |
|
|
47 |
} |
|
|
48 |
|
|
|
49 |
.right-column { |
|
|
50 |
width: 65%; |
|
|
51 |
padding: 20px; |
|
|
52 |
display: flex; |
|
|
53 |
flex-direction: column; |
|
|
54 |
align-items: center; |
|
|
55 |
} |
|
|
56 |
|
|
|
57 |
h1 { |
|
|
58 |
text-align: center; |
|
|
59 |
font-size: 1.8rem; |
|
|
60 |
margin-bottom: 20px; |
|
|
61 |
color: #333; |
|
|
62 |
} |
|
|
63 |
|
|
|
64 |
input[type="file"] { |
|
|
65 |
margin: 15px 0; |
|
|
66 |
padding: 10px; |
|
|
67 |
border: 1px solid #ddd; |
|
|
68 |
border-radius: 6px; |
|
|
69 |
cursor: pointer; |
|
|
70 |
width: 100%; |
|
|
71 |
} |
|
|
72 |
|
|
|
73 |
button { |
|
|
74 |
padding: 12px 30px; |
|
|
75 |
background: #007bff; |
|
|
76 |
color: #fff; |
|
|
77 |
border: none; |
|
|
78 |
border-radius: 6px; |
|
|
79 |
font-size: 14px; |
|
|
80 |
cursor: pointer; |
|
|
81 |
transition: background 0.3s ease; |
|
|
82 |
margin: 10px 0; |
|
|
83 |
} |
|
|
84 |
|
|
|
85 |
button:hover { |
|
|
86 |
background: #0056b3; |
|
|
87 |
} |
|
|
88 |
|
|
|
89 |
.progress-bar { |
|
|
90 |
width: 100%; |
|
|
91 |
height: 10px; |
|
|
92 |
background: #e9ecef; |
|
|
93 |
border-radius: 5px; |
|
|
94 |
margin: 15px 0; |
|
|
95 |
overflow: hidden; |
|
|
96 |
position: relative; |
|
|
97 |
} |
|
|
98 |
|
|
|
99 |
.progress-bar .progress { |
|
|
100 |
height: 100%; |
|
|
101 |
background: #28a745; |
|
|
102 |
width: 0; |
|
|
103 |
transition: width 0.5s; |
|
|
104 |
} |
|
|
105 |
|
|
|
106 |
.fixed-box { |
|
|
107 |
margin-top: 20px; |
|
|
108 |
text-align: center; |
|
|
109 |
border: 1px solid #ddd; |
|
|
110 |
border-radius: 10px; |
|
|
111 |
padding: 10px; |
|
|
112 |
background: #fff; |
|
|
113 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|
|
114 |
} |
|
|
115 |
|
|
|
116 |
.fixed-box img { |
|
|
117 |
max-width: 100%; |
|
|
118 |
border-radius: 8px; |
|
|
119 |
} |
|
|
120 |
|
|
|
121 |
.results { |
|
|
122 |
font-size: 1rem; |
|
|
123 |
margin-bottom: 20px; |
|
|
124 |
text-align: center; |
|
|
125 |
display: none; |
|
|
126 |
} |
|
|
127 |
|
|
|
128 |
.results.visible { |
|
|
129 |
display: block; |
|
|
130 |
} |
|
|
131 |
|
|
|
132 |
.note { |
|
|
133 |
font-size: 1rem; |
|
|
134 |
color: #d9534f; |
|
|
135 |
text-align: center; |
|
|
136 |
margin-top: 20px; |
|
|
137 |
} |
|
|
138 |
|
|
|
139 |
.visualization-group { |
|
|
140 |
margin-top: 20px; |
|
|
141 |
width: 100%; |
|
|
142 |
display: none; |
|
|
143 |
} |
|
|
144 |
|
|
|
145 |
.visualization-group.visible { |
|
|
146 |
display: block; |
|
|
147 |
} |
|
|
148 |
|
|
|
149 |
.visualization-group h3 { |
|
|
150 |
font-size: 1.2rem; |
|
|
151 |
margin-bottom: 10px; |
|
|
152 |
text-align: center; |
|
|
153 |
} |
|
|
154 |
|
|
|
155 |
.visualization { |
|
|
156 |
display: grid; |
|
|
157 |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
|
|
158 |
gap: 15px; |
|
|
159 |
justify-items: center; |
|
|
160 |
} |
|
|
161 |
|
|
|
162 |
.visualization img { |
|
|
163 |
max-width: 100%; |
|
|
164 |
border-radius: 8px; |
|
|
165 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|
|
166 |
transition: transform 0.2s ease; |
|
|
167 |
} |
|
|
168 |
|
|
|
169 |
.visualization img:hover { |
|
|
170 |
transform: scale(1.05); |
|
|
171 |
} |
|
|
172 |
|
|
|
173 |
.image-name { |
|
|
174 |
text-align: center; |
|
|
175 |
font-size: 0.9rem; |
|
|
176 |
margin-top: 5px; |
|
|
177 |
color: #555; |
|
|
178 |
} |
|
|
179 |
|
|
|
180 |
/* Prediction Result Box */ |
|
|
181 |
.prediction-box { |
|
|
182 |
padding: 20px; |
|
|
183 |
background-color: #f8f9fa; |
|
|
184 |
border-radius: 8px; |
|
|
185 |
border: 1px solid #ddd; |
|
|
186 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|
|
187 |
margin-top: 20px; |
|
|
188 |
text-align: center; |
|
|
189 |
} |
|
|
190 |
|
|
|
191 |
.prediction-box p { |
|
|
192 |
font-size: 1.2rem; |
|
|
193 |
margin: 5px 0; |
|
|
194 |
} |
|
|
195 |
|
|
|
196 |
.prediction-box .class { |
|
|
197 |
font-weight: bold; |
|
|
198 |
font-size: 1.5rem; |
|
|
199 |
color: #007bff; |
|
|
200 |
} |
|
|
201 |
|
|
|
202 |
.prediction-box .confidence { |
|
|
203 |
font-size: 1.1rem; |
|
|
204 |
color: #28a745; |
|
|
205 |
} |
|
|
206 |
</style> |
|
|
207 |
</head> |
|
|
208 |
<body> |
|
|
209 |
<div class="container"> |
|
|
210 |
<!-- Left Column --> |
|
|
211 |
<div class="left-column"> |
|
|
212 |
<h1>Upload X-Ray</h1> |
|
|
213 |
<form id="upload-form"> |
|
|
214 |
<input type="file" id="file-input" name="file" accept="image/*" required> |
|
|
215 |
<button type="submit">Predict</button> |
|
|
216 |
<button id="refresh-button">Refresh</button> |
|
|
217 |
<div class="progress-bar"> |
|
|
218 |
<div class="progress" id="progress-bar"></div> |
|
|
219 |
</div> |
|
|
220 |
</form> |
|
|
221 |
<div class="fixed-box"> |
|
|
222 |
<h3>Uploaded Image</h3> |
|
|
223 |
<img id="original" src="https://via.placeholder.com/150?text=Upload+Image" alt="Uploaded Image"> |
|
|
224 |
<div class="image-name" id="image-name">Image Name</div> |
|
|
225 |
</div> |
|
|
226 |
</div> |
|
|
227 |
|
|
|
228 |
<!-- Right Column --> |
|
|
229 |
<div class="right-column"> |
|
|
230 |
<div class="prediction-box" id="result-box"> |
|
|
231 |
<p><strong>Predicted Class:</strong> <span id="predicted-class">N/A</span></p> |
|
|
232 |
<p><strong>Confidence Score:</strong> <span id="confidence-score">N/A</span></p> |
|
|
233 |
</div> |
|
|
234 |
<div id="note" class="note" style="display: none;"> |
|
|
235 |
Sorry, you uploaded a non chest X-ray image. Please upload a chest X-ray image. Thank you! |
|
|
236 |
</div> |
|
|
237 |
<div class="visualization-group" id="visualization-group"> |
|
|
238 |
<h3>Image Visualizations</h3> |
|
|
239 |
<div class="visualization"> |
|
|
240 |
<div> |
|
|
241 |
<img id="grayscale" src="https://via.placeholder.com/150?text=Grayscale" alt="Grayscale Image"> |
|
|
242 |
<div class="image-name">Grayscale</div> |
|
|
243 |
</div> |
|
|
244 |
<div> |
|
|
245 |
<img id="equalized" src="https://via.placeholder.com/150?text=Equalized" alt="Equalized Image"> |
|
|
246 |
<div class="image-name">Equalized</div> |
|
|
247 |
</div> |
|
|
248 |
<div> |
|
|
249 |
<img id="edges" src="https://via.placeholder.com/150?text=Edges" alt="Edge Detection"> |
|
|
250 |
<div class="image-name">Edges</div> |
|
|
251 |
</div> |
|
|
252 |
<div> |
|
|
253 |
<img id="segmented" src="https://via.placeholder.com/150?text=Segmented" alt="Segmented Image"> |
|
|
254 |
<div class="image-name">Segmented</div> |
|
|
255 |
</div> |
|
|
256 |
</div> |
|
|
257 |
<h3>Advanced Visualizations</h3> |
|
|
258 |
<div class="visualization"> |
|
|
259 |
<div> |
|
|
260 |
<img id="grad_cam" src="https://via.placeholder.com/150?text=Grad-CAM" alt="Grad-CAM Visualization"> |
|
|
261 |
<div class="image-name">Grad-CAM</div> |
|
|
262 |
</div> |
|
|
263 |
<div> |
|
|
264 |
<img id="roi" src="https://via.placeholder.com/150?text=ROI" alt="Region of Interest"> |
|
|
265 |
<div class="image-name">ROI</div> |
|
|
266 |
</div> |
|
|
267 |
</div> |
|
|
268 |
</div> |
|
|
269 |
</div> |
|
|
270 |
</div> |
|
|
271 |
|
|
|
272 |
<script> |
|
|
273 |
const form = document.getElementById('upload-form'); |
|
|
274 |
const progressBar = document.getElementById('progress-bar'); |
|
|
275 |
const resultBox = document.getElementById('result-box'); |
|
|
276 |
const refreshButton = document.getElementById('refresh-button'); |
|
|
277 |
const note = document.getElementById('note'); |
|
|
278 |
const visualizationGroup = document.getElementById('visualization-group'); |
|
|
279 |
const imageName = document.getElementById('image-name'); |
|
|
280 |
|
|
|
281 |
form.addEventListener('submit', async (event) => { |
|
|
282 |
event.preventDefault(); |
|
|
283 |
|
|
|
284 |
progressBar.style.width = '0'; |
|
|
285 |
resultBox.style.display = 'none'; |
|
|
286 |
note.style.display = 'none'; |
|
|
287 |
visualizationGroup.style.display = 'none'; |
|
|
288 |
const formData = new FormData(form); |
|
|
289 |
|
|
|
290 |
// Simulate progress |
|
|
291 |
let progress = 0; |
|
|
292 |
const interval = setInterval(() => { |
|
|
293 |
if (progress < 100) { |
|
|
294 |
progress += 10; |
|
|
295 |
progressBar.style.width = `${progress}%`; |
|
|
296 |
} else { |
|
|
297 |
clearInterval(interval); |
|
|
298 |
} |
|
|
299 |
}, 100); |
|
|
300 |
|
|
|
301 |
const response = await fetch('/predict', { method: 'POST', body: formData }); |
|
|
302 |
const data = await response.json(); |
|
|
303 |
|
|
|
304 |
document.getElementById('original').src = URL.createObjectURL(formData.get('file')); |
|
|
305 |
imageName.textContent = formData.get('file').name; |
|
|
306 |
|
|
|
307 |
if (data.error) { |
|
|
308 |
resultBox.style.display = 'block'; |
|
|
309 |
document.getElementById('predicted-class').textContent = 'N/A'; |
|
|
310 |
document.getElementById('confidence-score').textContent = 'N/A'; |
|
|
311 |
note.style.display = 'block'; |
|
|
312 |
return; |
|
|
313 |
} |
|
|
314 |
|
|
|
315 |
resultBox.style.display = 'block'; |
|
|
316 |
document.getElementById('predicted-class').textContent = data.predicted_class; |
|
|
317 |
document.getElementById('confidence-score').textContent = `${data.confidence_score}%`; |
|
|
318 |
visualizationGroup.style.display = 'block'; |
|
|
319 |
|
|
|
320 |
document.getElementById('grayscale').src = data.visualizations.grayscale; |
|
|
321 |
document.getElementById('equalized').src = data.visualizations.equalized; |
|
|
322 |
document.getElementById('edges').src = data.visualizations.edges; |
|
|
323 |
document.getElementById('segmented').src = data.visualizations.segmented; |
|
|
324 |
document.getElementById('grad_cam').src = data.visualizations.grad_cam; |
|
|
325 |
document.getElementById('roi').src = data.visualizations.roi; |
|
|
326 |
}); |
|
|
327 |
|
|
|
328 |
refreshButton.addEventListener('click', () => { |
|
|
329 |
window.location.reload(); |
|
|
330 |
}); |
|
|
331 |
</script> |
|
|
332 |
</body> |
|
|
333 |
</html> |