Spaces:
Sleeping
Sleeping
Deploy KidneyDL CT Scan Classifier
Browse files- src/cnnClassifier/pipeline/prediction.py +12 -2
- templates/index.html +33 -3
src/cnnClassifier/pipeline/prediction.py
CHANGED
|
@@ -3,6 +3,10 @@ from tensorflow.keras.models import load_model
|
|
| 3 |
from tensorflow.keras.preprocessing import image
|
| 4 |
import os
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
class PredictionPipeline:
|
| 8 |
def __init__(self, filename, model=None):
|
|
@@ -23,6 +27,12 @@ class PredictionPipeline:
|
|
| 23 |
img_array = image.img_to_array(img)
|
| 24 |
img_array = np.expand_dims(img_array, axis=0) / 255.0
|
| 25 |
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
return [{"image": "Tumor" if
|
|
|
|
|
|
| 3 |
from tensorflow.keras.preprocessing import image
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
# Minimum softmax confidence required to trust a prediction.
|
| 7 |
+
# Below this threshold the image is likely not a kidney CT scan.
|
| 8 |
+
CONFIDENCE_THRESHOLD = 0.80
|
| 9 |
+
|
| 10 |
|
| 11 |
class PredictionPipeline:
|
| 12 |
def __init__(self, filename, model=None):
|
|
|
|
| 27 |
img_array = image.img_to_array(img)
|
| 28 |
img_array = np.expand_dims(img_array, axis=0) / 255.0
|
| 29 |
|
| 30 |
+
predictions = model.predict(img_array)
|
| 31 |
+
confidence = float(np.max(predictions))
|
| 32 |
+
class_idx = int(np.argmax(predictions, axis=1)[0])
|
| 33 |
+
|
| 34 |
+
if confidence < CONFIDENCE_THRESHOLD:
|
| 35 |
+
return [{"image": "InvalidImage", "confidence": round(confidence, 4)}]
|
| 36 |
|
| 37 |
+
return [{"image": "Tumor" if class_idx == 1 else "Normal",
|
| 38 |
+
"confidence": round(confidence, 4)}]
|
templates/index.html
CHANGED
|
@@ -24,6 +24,9 @@
|
|
| 24 |
--danger: #ef4444;
|
| 25 |
--danger-bg: #fef2f2;
|
| 26 |
--danger-bdr: #fca5a5;
|
|
|
|
|
|
|
|
|
|
| 27 |
--shadow: 0 4px 32px rgba(15,23,42,0.08);
|
| 28 |
--shadow-lg: 0 8px 48px rgba(15,23,42,0.14);
|
| 29 |
--radius: 18px;
|
|
@@ -46,6 +49,9 @@
|
|
| 46 |
--danger: #f87171;
|
| 47 |
--danger-bg: #2d0a0a;
|
| 48 |
--danger-bdr: #7f1d1d;
|
|
|
|
|
|
|
|
|
|
| 49 |
--shadow: 0 4px 32px rgba(0,0,0,0.45);
|
| 50 |
--shadow-lg: 0 8px 48px rgba(0,0,0,0.6);
|
| 51 |
}
|
|
@@ -250,8 +256,11 @@
|
|
| 250 |
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
| 251 |
to { opacity: 1; transform: translateY(0) scale(1); }
|
| 252 |
}
|
| 253 |
-
#result.normal
|
| 254 |
-
#result.tumor
|
|
|
|
|
|
|
|
|
|
| 255 |
.res-row { display: flex; align-items: flex-start; gap: 14px; }
|
| 256 |
.res-ico { font-size: 1.9rem; flex-shrink: 0; line-height: 1; }
|
| 257 |
.res-title { font-size: 1.15rem; font-weight: 800; margin-bottom: 3px; }
|
|
@@ -408,7 +417,17 @@
|
|
| 408 |
|
| 409 |
<div>
|
| 410 |
<div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()">
|
| 411 |
-
<div class="dz-icon">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
<p class="dz-hint">
|
| 413 |
Drop your CT scan image here<br/>
|
| 414 |
or <b>click to choose a file</b>
|
|
@@ -674,6 +693,17 @@
|
|
| 674 |
const pred = data[0]?.image || 'Unknown';
|
| 675 |
|
| 676 |
const resultEl = document.getElementById('result');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
const conf = (pred === 'Tumor'
|
| 678 |
? 87 + Math.random() * 11
|
| 679 |
: 85 + Math.random() * 13).toFixed(1);
|
|
|
|
| 24 |
--danger: #ef4444;
|
| 25 |
--danger-bg: #fef2f2;
|
| 26 |
--danger-bdr: #fca5a5;
|
| 27 |
+
--warning: #f59e0b;
|
| 28 |
+
--warning-bg: #fffbeb;
|
| 29 |
+
--warning-bdr: #fcd34d;
|
| 30 |
--shadow: 0 4px 32px rgba(15,23,42,0.08);
|
| 31 |
--shadow-lg: 0 8px 48px rgba(15,23,42,0.14);
|
| 32 |
--radius: 18px;
|
|
|
|
| 49 |
--danger: #f87171;
|
| 50 |
--danger-bg: #2d0a0a;
|
| 51 |
--danger-bdr: #7f1d1d;
|
| 52 |
+
--warning: #fbbf24;
|
| 53 |
+
--warning-bg: #1c1500;
|
| 54 |
+
--warning-bdr: #78350f;
|
| 55 |
--shadow: 0 4px 32px rgba(0,0,0,0.45);
|
| 56 |
--shadow-lg: 0 8px 48px rgba(0,0,0,0.6);
|
| 57 |
}
|
|
|
|
| 256 |
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
| 257 |
to { opacity: 1; transform: translateY(0) scale(1); }
|
| 258 |
}
|
| 259 |
+
#result.normal { background: var(--success-bg); border: 1px solid var(--success-bdr); }
|
| 260 |
+
#result.tumor { background: var(--danger-bg); border: 1px solid var(--danger-bdr); }
|
| 261 |
+
#result.invalid { background: var(--warning-bg); border: 1px solid var(--warning-bdr); }
|
| 262 |
+
#result.invalid .res-title { color: var(--warning); }
|
| 263 |
+
#result.invalid .conf-wrap { display: none; }
|
| 264 |
.res-row { display: flex; align-items: flex-start; gap: 14px; }
|
| 265 |
.res-ico { font-size: 1.9rem; flex-shrink: 0; line-height: 1; }
|
| 266 |
.res-title { font-size: 1.15rem; font-weight: 800; margin-bottom: 3px; }
|
|
|
|
| 417 |
|
| 418 |
<div>
|
| 419 |
<div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()">
|
| 420 |
+
<div class="dz-icon">
|
| 421 |
+
<!-- Kidney bean icon: convex lateral side, concave medial (hilum) side -->
|
| 422 |
+
<svg width="52" height="64" viewBox="0 0 52 64" fill="none" stroke="currentColor"
|
| 423 |
+
stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
|
| 424 |
+
style="opacity:0.55;display:block;margin:0 auto 4px">
|
| 425 |
+
<path d="M26 4 C41 4 49 15 49 29 C49 46 41 60 26 61
|
| 426 |
+
C15 60 7 54 5 45 C3 38 5 31 9 28
|
| 427 |
+
C12 25 12 22 9 18 C6 14 10 4 26 4 Z"/>
|
| 428 |
+
<path d="M12 29 C15 24 15 18 12 13" stroke-width="2.2"/>
|
| 429 |
+
</svg>
|
| 430 |
+
</div>
|
| 431 |
<p class="dz-hint">
|
| 432 |
Drop your CT scan image here<br/>
|
| 433 |
or <b>click to choose a file</b>
|
|
|
|
| 693 |
const pred = data[0]?.image || 'Unknown';
|
| 694 |
|
| 695 |
const resultEl = document.getElementById('result');
|
| 696 |
+
|
| 697 |
+
if (pred === 'InvalidImage') {
|
| 698 |
+
resultEl.className = 'invalid';
|
| 699 |
+
document.getElementById('resIco').textContent = '\u26A0\uFE0F';
|
| 700 |
+
document.getElementById('resTitle').textContent = 'Wrong Image Detected';
|
| 701 |
+
document.getElementById('resSub').textContent =
|
| 702 |
+
'Sorry — this does not appear to be a kidney CT scan. Please upload a valid grayscale CT scan of a kidney and try again.';
|
| 703 |
+
resultEl.style.display = 'block';
|
| 704 |
+
return;
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
const conf = (pred === 'Tumor'
|
| 708 |
? 87 + Math.random() * 11
|
| 709 |
: 85 + Math.random() * 13).toFixed(1);
|