Spaces:
Sleeping
Sleeping
Deploy KidneyDL CT Scan Classifier
Browse files- .gitattributes +1 -0
- app.py +24 -6
- artifacts/training/model.keras +3 -0
- requirements.txt +2 -2
- resave_model.py +21 -0
- src/cnnClassifier/pipeline/prediction.py +4 -1
- templates/index.html +3 -2
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* 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
|
|
|
|
|
|
| 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
|
| 36 |
+
artifacts/training/model.keras filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
from flask import Flask, request, jsonify, render_template
|
| 3 |
from flask_cors import CORS
|
| 4 |
from cnnClassifier.pipeline.prediction import PredictionPipeline
|
|
@@ -9,12 +10,24 @@ CORS(app)
|
|
| 9 |
UPLOAD_FOLDER = "uploads"
|
| 10 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 11 |
|
|
|
|
|
|
|
| 12 |
|
| 13 |
@app.route("/", methods=["GET"])
|
| 14 |
def home():
|
| 15 |
return render_template("index.html")
|
| 16 |
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
@app.route("/train", methods=["GET", "POST"])
|
| 19 |
def train():
|
| 20 |
os.system("python main.py")
|
|
@@ -31,12 +44,17 @@ def predict():
|
|
| 31 |
return jsonify({"error": "No file selected"}), 400
|
| 32 |
|
| 33 |
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
if __name__ == "__main__":
|
|
|
|
| 1 |
import os
|
| 2 |
+
import traceback
|
| 3 |
from flask import Flask, request, jsonify, render_template
|
| 4 |
from flask_cors import CORS
|
| 5 |
from cnnClassifier.pipeline.prediction import PredictionPipeline
|
|
|
|
| 10 |
UPLOAD_FOLDER = "uploads"
|
| 11 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 12 |
|
| 13 |
+
MODEL_PATH = os.path.join("artifacts", "training", "model.h5")
|
| 14 |
+
|
| 15 |
|
| 16 |
@app.route("/", methods=["GET"])
|
| 17 |
def home():
|
| 18 |
return render_template("index.html")
|
| 19 |
|
| 20 |
|
| 21 |
+
@app.route("/health", methods=["GET"])
|
| 22 |
+
def health():
|
| 23 |
+
model_exists = os.path.isfile(MODEL_PATH)
|
| 24 |
+
return jsonify({
|
| 25 |
+
"status": "ok" if model_exists else "degraded",
|
| 26 |
+
"model_found": model_exists,
|
| 27 |
+
"model_path": os.path.abspath(MODEL_PATH),
|
| 28 |
+
}), 200 if model_exists else 503
|
| 29 |
+
|
| 30 |
+
|
| 31 |
@app.route("/train", methods=["GET", "POST"])
|
| 32 |
def train():
|
| 33 |
os.system("python main.py")
|
|
|
|
| 44 |
return jsonify({"error": "No file selected"}), 400
|
| 45 |
|
| 46 |
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 47 |
+
try:
|
| 48 |
+
file.save(filepath)
|
| 49 |
+
pipeline = PredictionPipeline(filepath)
|
| 50 |
+
result = pipeline.predict()
|
| 51 |
+
return jsonify(result)
|
| 52 |
+
except Exception as e:
|
| 53 |
+
traceback.print_exc()
|
| 54 |
+
return jsonify({"error": str(e)}), 500
|
| 55 |
+
finally:
|
| 56 |
+
if os.path.exists(filepath):
|
| 57 |
+
os.remove(filepath)
|
| 58 |
|
| 59 |
|
| 60 |
if __name__ == "__main__":
|
artifacts/training/model.keras
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e92b2657c65573f928f9f5f20f63db4001cc93d8ae1ed58f5f396918b0a2ebda
|
| 3 |
+
size 59141294
|
requirements.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
tensorflow
|
| 2 |
-
keras
|
| 3 |
dvc
|
| 4 |
numpy
|
| 5 |
pandas
|
|
|
|
| 1 |
+
tensorflow==2.20.0
|
| 2 |
+
keras==3.13.2
|
| 3 |
dvc
|
| 4 |
numpy
|
| 5 |
pandas
|
resave_model.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
One-time script: resave model.h5 as model.keras (Keras 3 native format).
|
| 3 |
+
This ensures the deployed model loads correctly regardless of minor Keras version
|
| 4 |
+
differences between training and serving environments.
|
| 5 |
+
|
| 6 |
+
Run once:
|
| 7 |
+
python resave_model.py
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from tensorflow.keras.models import load_model
|
| 11 |
+
|
| 12 |
+
h5_path = "artifacts/training/model.h5"
|
| 13 |
+
keras_path = "artifacts/training/model.keras"
|
| 14 |
+
|
| 15 |
+
print(f"Loading {h5_path} ...")
|
| 16 |
+
model = load_model(h5_path)
|
| 17 |
+
|
| 18 |
+
print(f"Saving {keras_path} ...")
|
| 19 |
+
model.save(keras_path)
|
| 20 |
+
|
| 21 |
+
print("Done. Deploy with: python deploy_to_hf.py")
|
src/cnnClassifier/pipeline/prediction.py
CHANGED
|
@@ -9,7 +9,10 @@ class PredictionPipeline:
|
|
| 9 |
self.filename = filename
|
| 10 |
|
| 11 |
def predict(self):
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
img = image.load_img(self.filename, target_size=(224, 224))
|
| 15 |
img_array = image.img_to_array(img)
|
|
|
|
| 9 |
self.filename = filename
|
| 10 |
|
| 11 |
def predict(self):
|
| 12 |
+
keras_path = os.path.join("artifacts", "training", "model.keras")
|
| 13 |
+
h5_path = os.path.join("artifacts", "training", "model.h5")
|
| 14 |
+
model_path = keras_path if os.path.isfile(keras_path) else h5_path
|
| 15 |
+
model = load_model(model_path)
|
| 16 |
|
| 17 |
img = image.load_img(self.filename, target_size=(224, 224))
|
| 18 |
img_array = image.img_to_array(img)
|
templates/index.html
CHANGED
|
@@ -670,6 +670,7 @@
|
|
| 670 |
try {
|
| 671 |
const res = await fetch('/predict', { method: 'POST', body: fd });
|
| 672 |
const data = await res.json();
|
|
|
|
| 673 |
const pred = data[0]?.image || 'Unknown';
|
| 674 |
|
| 675 |
const resultEl = document.getElementById('result');
|
|
@@ -695,8 +696,8 @@
|
|
| 695 |
document.getElementById('confPct').textContent = conf + '%';
|
| 696 |
resultEl.style.display = 'block';
|
| 697 |
|
| 698 |
-
} catch {
|
| 699 |
-
alert('Something went wrong during analysis.
|
| 700 |
} finally {
|
| 701 |
document.getElementById('loading').style.display = 'none';
|
| 702 |
document.getElementById('predictBtn').disabled = false;
|
|
|
|
| 670 |
try {
|
| 671 |
const res = await fetch('/predict', { method: 'POST', body: fd });
|
| 672 |
const data = await res.json();
|
| 673 |
+
if (!res.ok) throw new Error(data.error || `Server error ${res.status}`);
|
| 674 |
const pred = data[0]?.image || 'Unknown';
|
| 675 |
|
| 676 |
const resultEl = document.getElementById('result');
|
|
|
|
| 696 |
document.getElementById('confPct').textContent = conf + '%';
|
| 697 |
resultEl.style.display = 'block';
|
| 698 |
|
| 699 |
+
} catch (err) {
|
| 700 |
+
alert('Something went wrong during analysis.\n\n' + (err?.message || err));
|
| 701 |
} finally {
|
| 702 |
document.getElementById('loading').style.display = 'none';
|
| 703 |
document.getElementById('predictBtn').disabled = false;
|