File size: 14,154 Bytes
1d4eab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64f7a3e
 
1d4eab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
07e775a
1d4eab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
07e775a
1d4eab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
import os
import uuid
import json
import numpy as np
import cv2
from PIL import Image
from flask import Flask, render_template, request, redirect, url_for, session, flash
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"

import tensorflow as tf
keras = tf.keras

# Monkeypatch Keras 3 Layer.__init__ to ignore unrecognized quantization_config argument
# which is present in legacy models but unsupported by newer Keras 3 versions.
original_layer_init = keras.layers.Layer.__init__
def patched_layer_init(self, *args, **kwargs):
    kwargs.pop('quantization_config', None)
    original_layer_init(self, *args, **kwargs)
keras.layers.Layer.__init__ = patched_layer_init

load_model = keras.models.load_model

# ──────────────────────────────────────────────
# Flask App Setup
# ──────────────────────────────────────────────

app = Flask(__name__)
app.secret_key = os.environ.get("SECRET_KEY", "ojos_ai_secret_2024_secure")

UPLOAD_FOLDER = os.path.join("static", "uploads")
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024  # 10 MB max upload

# ──────────────────────────────────────────────
# Load Model
# ──────────────────────────────────────────────

MODEL_PATH   = "best_eye_disease_model.h5"
INDICES_PATH = "class_indices.json"

print("Loading OJOS AI model (96.69% accuracy)...")
eye_model = load_model(MODEL_PATH, compile=False)

with open(INDICES_PATH, "r") as f:
    class_to_idx = json.load(f)

# Invert: idx β†’ class_name
CLASS_NAMES = {v: k for k, v in class_to_idx.items()}
print(f"Model loaded! Classes: {CLASS_NAMES}")

# ──────────────────────────────────────────────
# Disease Info Dictionary
# ──────────────────────────────────────────────

DISEASE_INFO = {
    "cataract": {
        "display": "Cataract Detected",
        "emoji": "πŸ‘οΈ",
        "advice": "Cataracts cause clouding of the eye's lens leading to blurry vision. Early surgical treatment (phacoemulsification) is highly effective. Please consult an ophthalmologist at the earliest.",
        "severity": "warning",
        "color": "#f59e0b",
        "tips": [
            "Avoid prolonged exposure to UV light",
            "Schedule a comprehensive eye exam",
            "Surgery is safe and highly effective",
            "Wear UV-protective sunglasses outdoors"
        ]
    },
    "diabetic_retinopathy": {
        "display": "Diabetic Retinopathy",
        "emoji": "⚠️",
        "advice": "Diabetic retinopathy damages blood vessels in the retina due to high blood sugar. Strict blood sugar control and regular eye exams are critical. Consult both an endocrinologist and a retinal specialist immediately.",
        "severity": "danger",
        "color": "#ef4444",
        "tips": [
            "Control blood sugar levels strictly",
            "Get retinal exams every 6 months",
            "Maintain healthy blood pressure",
            "Laser treatment may be required"
        ]
    },
    "glaucoma": {
        "display": "Glaucoma Detected",
        "emoji": "πŸ”΄",
        "advice": "Glaucoma damages the optic nerve, often due to elevated eye pressure. It can lead to permanent vision loss if untreated. See an ophthalmologist immediately for pressure testing and treatment.",
        "severity": "danger",
        "color": "#ef4444",
        "tips": [
            "Get intraocular pressure checked immediately",
            "Eye drops or surgery may be needed",
            "Regular monitoring is essential",
            "Early detection prevents blindness"
        ]
    },
    "healthy": {
        "display": "Healthy Eye",
        "emoji": "βœ…",
        "advice": "Your retinal image appears normal with no signs of eye disease. Continue annual eye checkups and maintain a healthy lifestyle to preserve your vision for years to come.",
        "severity": "success",
        "color": "#10b981",
        "tips": [
            "Continue annual eye checkups",
            "Maintain a healthy diet rich in vitamins",
            "Limit screen time and take breaks",
            "Protect eyes from UV radiation"
        ]
    },
    "myopia": {
        "display": "Myopia Detected",
        "emoji": "πŸ‘“",
        "advice": "Myopia (nearsightedness) causes difficulty seeing distant objects clearly. Corrective lenses, contact lenses, or LASIK surgery can restore clear vision. Consult an optometrist for the best treatment option.",
        "severity": "info",
        "color": "#3b82f6",
        "tips": [
            "Get prescription glasses or contacts",
            "LASIK surgery is an effective option",
            "Take regular breaks from screens",
            "Spend more time outdoors β€” proven to slow progression"
        ]
    }
}

# ──────────────────────────────────────────────
# Database Setup
# ──────────────────────────────────────────────

def get_db():
    db_path = "/tmp/users.db" if os.name != "nt" else "users.db"
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    conn = get_db()
    conn.execute("""
        CREATE TABLE IF NOT EXISTS users (
            id       INTEGER PRIMARY KEY AUTOINCREMENT,
            name     TEXT    NOT NULL,
            email    TEXT    UNIQUE NOT NULL,
            password TEXT    NOT NULL
        )
    """)
    conn.commit()
    conn.close()

init_db()

# ──────────────────────────────────────────────
# CLAHE Preprocessing (matches training pipeline)
# ──────────────────────────────────────────────

def apply_clahe(img_uint8):
    """CLAHE contrast enhancement on LAB L-channel (same as training)."""
    lab = cv2.cvtColor(img_uint8, cv2.COLOR_RGB2LAB)
    l, a, b = cv2.split(lab)
    clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
    cl = clahe.apply(l)
    merged = cv2.merge((cl, a, b))
    return cv2.cvtColor(merged, cv2.COLOR_LAB2RGB)

def preprocess(img_pil):
    """Preprocess a PIL image β†’ model-ready numpy array."""
    img = np.array(img_pil.convert("RGB"))
    img = cv2.resize(img, (300, 300))
    img_clahe = apply_clahe(img.astype(np.uint8))
    from tensorflow.keras.applications.efficientnet_v2 import preprocess_input
    img_final = preprocess_input(img_clahe.astype(np.float32))
    return np.expand_dims(img_final, axis=0)

# ──────────────────────────────────────────────
# Routes β€” Pages
# ──────────────────────────────────────────────

@app.route("/")
def home():
    return render_template("home.html")

@app.route("/about")
def about():
    return render_template("about.html")

@app.route("/diseases_info")
def diseases_info():
    return render_template("diseases_info.html")

# ──────────────────────────────────────────────
# Routes β€” Auth
# ──────────────────────────────────────────────

@app.route("/login", methods=["GET", "POST"])
def login():
    if session.get("logged_in"):
        return redirect(url_for("prediction"))

    if request.method == "POST":
        email    = request.form.get("email", "").strip().lower()
        password = request.form.get("password", "")

        conn = get_db()
        user = conn.execute("SELECT * FROM users WHERE email = ?", (email,)).fetchone()
        conn.close()

        if not user:
            flash("No account found with this email. Please sign up first.", "danger")
            return redirect(url_for("login"))

        if not check_password_hash(user["password"], password):
            flash("Incorrect password. Please try again.", "danger")
            return redirect(url_for("login"))

        session.update({
            "logged_in":  True,
            "user_id":    user["id"],
            "user_name":  user["name"],
            "user_email": user["email"]
        })
        flash(f"Welcome back, {user['name']}! πŸ‘‹", "success")
        return redirect(url_for("prediction"))

    return render_template("login.html")

@app.route("/signup", methods=["POST"])
def signup():
    name     = request.form.get("name", "").strip()
    email    = request.form.get("email", "").strip().lower()
    password = request.form.get("password", "")

    if not name or not email or not password:
        flash("All fields are required.", "danger")
        return redirect(url_for("login"))

    if len(password) < 6:
        flash("Password must be at least 6 characters.", "warning")
        return redirect(url_for("login"))

    conn = get_db()
    existing = conn.execute("SELECT id FROM users WHERE email = ?", (email,)).fetchone()

    if existing:
        conn.close()
        flash("Email already registered. Please log in.", "warning")
        return redirect(url_for("login"))

    conn.execute(
        "INSERT INTO users (name, email, password) VALUES (?, ?, ?)",
        (name, email, generate_password_hash(password))
    )
    conn.commit()
    conn.close()

    flash("Account created! Please log in.", "success")
    return redirect(url_for("login"))

@app.route("/logout")
def logout():
    session.clear()
    flash("Logged out successfully.", "success")
    return redirect(url_for("home"))

# ──────────────────────────────────────────────
# Routes β€” Prediction
# ──────────────────────────────────────────────

@app.route("/prediction", methods=["GET", "POST"])
def prediction():
    if not session.get("logged_in"):
        flash("Please log in to access predictions.", "warning")
        return redirect(url_for("login"))

    result = None

    if request.method == "POST":
        file = request.files.get("retina_image")

        if not file or file.filename == "":
            flash("Please select an image file.", "warning")
            return redirect(url_for("prediction"))

        # Validate file extension
        allowed = {"jpg", "jpeg", "png", "bmp", "webp"}
        ext = file.filename.rsplit(".", 1)[-1].lower()
        if ext not in allowed:
            flash("Invalid file type. Please upload JPG, PNG or BMP.", "danger")
            return redirect(url_for("prediction"))

        try:
            image = Image.open(file).convert("RGB")
        except Exception:
            flash("Could not read image. Please upload a valid image file.", "danger")
            return redirect(url_for("prediction"))

        # Save uploaded image for preview
        img_filename = f"{uuid.uuid4().hex}.jpg"
        img_save_path = os.path.join(UPLOAD_FOLDER, img_filename)
        image.save(img_save_path, "JPEG", quality=85)
        img_url = url_for("static", filename=f"uploads/{img_filename}")

        # Preprocess & predict
        processed = preprocess(image)
        preds = eye_model.predict(processed, verbose=0)[0]

        # Top prediction
        pred_idx   = int(np.argmax(preds))
        confidence = float(preds[pred_idx]) * 100
        pred_class = CLASS_NAMES.get(pred_idx, "unknown")

        # All 5 class probabilities (sorted desc)
        all_probs = [
            {
                "name":  CLASS_NAMES.get(i, "?").replace("_", " ").title(),
                "key":   CLASS_NAMES.get(i, "?"),
                "prob":  round(float(preds[i]) * 100, 1),
                "color": DISEASE_INFO.get(CLASS_NAMES.get(i, ""), {}).get("color", "#6b7280")
            }
            for i in np.argsort(preds)[::-1]
        ]

        disease_data = DISEASE_INFO.get(pred_class, {
            "display":  pred_class.replace("_", " ").title(),
            "emoji":    "πŸ”",
            "advice":   "Please consult an eye specialist for proper diagnosis.",
            "severity": "warning",
            "color":    "#f59e0b",
            "tips":     ["Consult an ophthalmologist for detailed examination"]
        })

        result = {
            "disease":    disease_data["display"],
            "emoji":      disease_data["emoji"],
            "confidence": round(confidence, 2),
            "advice":     disease_data["advice"],
            "severity":   disease_data["severity"],
            "color":      disease_data["color"],
            "tips":       disease_data.get("tips", []),
            "all_probs":  all_probs,
            "img_url":    img_url,
        }

    return render_template(
        "prediction.html",
        result=result,
        user_name=session.get("user_name")
    )

# ──────────────────────────────────────────────
# Run
# ──────────────────────────────────────────────

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 5000))
    app.run(host="0.0.0.0", port=port, debug=False)