Spaces:
Running
Running
| import tensorflow as tf | |
| import gradio as gr | |
| import numpy as np | |
| import os | |
| import warnings | |
| from PIL import Image | |
| warnings.filterwarnings("ignore") | |
| # ============================================================ | |
| # 1. LOAD MODEL | |
| # ============================================================ | |
| print("=" * 60) | |
| print("LOADING MODEL") | |
| print("=" * 60) | |
| MODEL_PATH = "model.keras" | |
| try: | |
| best_model = tf.keras.models.load_model( | |
| MODEL_PATH, | |
| custom_objects={"quantization_config": None}, | |
| compile=False | |
| ) | |
| print("β Model berhasil dimuat") | |
| except Exception as e: | |
| print("β Pemuatan model gagal, membuat model dummy:", e) | |
| from tensorflow.keras import layers, Model | |
| inputs = layers.Input(shape=(224, 224, 3)) | |
| x = layers.GlobalAveragePooling2D()(inputs) | |
| dr_output = layers.Dense(5, name="dr_head")(x) # logits | |
| dme_output = layers.Dense(3, name="dme_head")(x) # logits | |
| best_model = Model(inputs, {"dr_head": dr_output, "dme_head": dme_output}) | |
| best_model.compile(optimizer="adam", loss="categorical_crossentropy") | |
| best_model.summary() | |
| # ============================================================ | |
| # 2. CONFIG | |
| # ============================================================ | |
| IMG_SIZE = 224 | |
| # ============================================================ | |
| # 3. PREPROCESSING | |
| # ============================================================ | |
| def preprocess_image(img_path): | |
| img = tf.io.read_file(img_path) | |
| img = tf.image.decode_jpeg(img, channels=3) | |
| img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE)) | |
| img = tf.cast(img, tf.float32) / 255.0 | |
| return tf.expand_dims(img, 0) | |
| # ============================================================ | |
| # 4. CLASS MAPPING | |
| # ============================================================ | |
| DR_CLASSES = ["No DR", "Mild", "Moderate", "Severe", "Proliferative DR"] | |
| DME_CLASSES = ["No DME", "Low Risk", "High Risk"] | |
| # ============================================================ | |
| # 5. SOFTMAX SAFETY | |
| # ============================================================ | |
| def ensure_probability(x): | |
| x = np.asarray(x, dtype=np.float32) | |
| if x.min() < 0 or x.max() > 1.0 or abs(x.sum() - 1.0) > 1e-3: | |
| x = tf.nn.softmax(x).numpy() | |
| return x | |
| # ============================================================ | |
| # 6. PREDICTION β TABLE ONLY | |
| # ============================================================ | |
| def predict_and_format_text(image_path): | |
| img_tensor = preprocess_image(image_path) | |
| preds = best_model.predict(img_tensor, verbose=0) | |
| # ---- handle output model ---- | |
| if isinstance(preds, dict): | |
| dr_key, dme_key = None, None | |
| for k in preds.keys(): | |
| lk = k.lower() | |
| if "dr" in lk: | |
| dr_key = k | |
| if "dme" in lk: | |
| dme_key = k | |
| dr_pred = preds[dr_key][0] | |
| dme_pred = preds[dme_key][0] | |
| elif isinstance(preds, (list, tuple)): | |
| dr_pred = preds[0][0] | |
| dme_pred = preds[1][0] | |
| elif isinstance(preds, np.ndarray): | |
| dr_pred = preds[0][:5] | |
| dme_pred = preds[0][5:] | |
| else: | |
| raise ValueError("Format output model tidak dikenali") | |
| # ---- softmax ---- | |
| dr_pred = ensure_probability(dr_pred) | |
| dme_pred = ensure_probability(dme_pred) | |
| # ---- result ---- | |
| dr_idx = int(np.argmax(dr_pred)) | |
| dme_idx = int(np.argmax(dme_pred)) | |
| dr_name = DR_CLASSES[dr_idx] | |
| dme_name = DME_CLASSES[dme_idx] | |
| dr_conf = dr_pred[dr_idx] * 100 | |
| dme_conf = dme_pred[dme_idx] * 100 | |
| # ====================================================== | |
| # π₯ JUDUL UTAMA | |
| # ====================================================== | |
| main_result = f""" | |
| <div style="text-align:center; font-size:30px; font-weight:800; margin-bottom:20px;"> | |
| HASIL PREDIKSI | |
| </div> | |
| """ | |
| # ====================================================== | |
| # π TABEL HASIL | |
| # ====================================================== | |
| table_result = f""" | |
| <table style="width:100%; border-collapse:collapse; font-size:16px;"> | |
| <thead> | |
| <tr style="background-color:#f2f2f2;"> | |
| <th style="border:1px solid #ccc; padding:8px;">Aspek</th> | |
| <th style="border:1px solid #ccc; padding:8px;">Klasifikasi</th> | |
| <th style="border:1px solid #ccc; padding:8px;">Confidence (%)</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td style="border:1px solid #ccc; padding:8px;">Diabetic Retinopathy (DR)</td> | |
| <td style="border:1px solid #ccc; padding:8px;"><b>{dr_name}</b></td> | |
| <td style="border:1px solid #ccc; padding:8px;">{dr_conf:.2f}%</td> | |
| </tr> | |
| <tr> | |
| <td style="border:1px solid #ccc; padding:8px;">Diabetic Macular Edema (DME)</td> | |
| <td style="border:1px solid #ccc; padding:8px;"><b>{dme_name}</b></td> | |
| <td style="border:1px solid #ccc; padding:8px;">{dme_conf:.2f}%</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| """ | |
| # ====================================================== | |
| # π©Ί REKOMENDASI (DINAMIS) | |
| # ====================================================== | |
| # Rekomendasi DR | |
| if dr_name in ["No DR"]: | |
| rec_dr = "Lanjutkan pola hidup sehat dan lakukan pemeriksaan mata rutin minimal 1 tahun sekali." | |
| elif dr_name in ["Mild", "Moderate"]: | |
| rec_dr = "Disarankan kontrol gula darah secara ketat dan pemeriksaan mata berkala setiap 6 bulan." | |
| else: # Severe / Proliferative | |
| rec_dr = "Disarankan segera konsultasi ke dokter spesialis mata untuk evaluasi dan penanganan lebih lanjut." | |
| # Rekomendasi DME | |
| if dme_name == "No DME": | |
| rec_dme = "Belum ditemukan tanda edema makula diabetik, lanjutkan pemantauan rutin." | |
| elif dme_name == "Low Risk": | |
| rec_dme = "Perlu observasi ketat dan pemeriksaan lanjutan untuk mencegah progresivitas." | |
| else: # High Risk | |
| rec_dme = "Disarankan segera mendapatkan evaluasi klinis dan terapi oleh dokter spesialis mata." | |
| recommendation = f""" | |
| <div style="margin-top:20px; padding:15px; border-left:5px solid #007bff; background-color:#f9f9f9;"> | |
| <b>π©Ί Rekomendasi Klinis:</b><br/><br/> | |
| <b>β’ Diabetic Retinopathy (DR):</b><br/> | |
| {rec_dr}<br/><br/> | |
| <b>β’ Diabetic Macular Edema (DME):</b><br/> | |
| {rec_dme} | |
| </div> | |
| """ | |
| return main_result + table_result + recommendation | |
| # ============================================================ | |
| # 7. MULTI TEST IMAGES | |
| # ============================================================ | |
| TEST_IMAGES = [ | |
| "IDRiD_001test.jpg", | |
| "IDRiD_004test.jpg", | |
| "IDRiD_005test.jpg", | |
| "IDRiD_006test.jpg", | |
| "IDRiD_007test.jpg", | |
| "IDRiD_008test.jpg", | |
| "IDRiD_009test.jpg", | |
| "IDRiD_010test.jpg", | |
| "IDRiD_011test.jpg", | |
| "IDRiD_012test.jpg", | |
| ] | |
| TEST_IMAGES = [[p] for p in TEST_IMAGES if os.path.exists(p)] | |
| # ============================================================ | |
| # 8. GRADIO WRAPPER | |
| # ============================================================ | |
| def gradio_predict(image): | |
| if image is None: | |
| return "β Silakan unggah gambar" | |
| temp_path = "uploaded.jpg" | |
| image.save(temp_path) | |
| return predict_and_format_text(temp_path) | |
| # ============================================================ | |
| # 9. GRADIO UI | |
| # ============================================================ | |
| with gr.Blocks(title="DR & DME Detection") as demo: | |
| gr.Markdown(""" | |
| # π©Ί Diabetic Retinopathy Image Dataset (IDRiD) | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| input_img = gr.Image(type="pil", label="Upload Gambar Retina") | |
| btn = gr.Button("π Prediksi") | |
| with gr.Column(): | |
| out_text = gr.Markdown() | |
| gr.Markdown("### π§ͺ Data Testing") | |
| gr.Examples( | |
| examples=TEST_IMAGES, | |
| inputs=input_img | |
| ) | |
| btn.click( | |
| fn=gradio_predict, | |
| inputs=input_img, | |
| outputs=out_text | |
| ) | |
| # ============================================================ | |
| # 10. LAUNCH | |
| # ============================================================ | |
| demo.launch(share=True) |