kodetr commited on
Commit
6efe549
·
verified ·
1 Parent(s): b3014b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +478 -113
app.py CHANGED
@@ -1,140 +1,505 @@
1
  import tensorflow as tf
2
  import gradio as gr
3
  import numpy as np
4
- from PIL import Image
5
- import io
6
  import os
 
 
 
 
 
7
 
8
- IMG_SIZE = 224
9
 
10
- # ===== DETECT GPU/CPU =====
11
- try:
12
- gpus = tf.config.list_physical_devices('GPU')
13
- if gpus:
14
- # Coba atur memory growth, tapi jangan crash jika gagal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  try:
16
- tf.config.experimental.set_memory_growth(gpus[0], True)
17
- except:
18
- pass
19
- print("GPU available")
20
- else:
21
- print("Using CPU")
22
- except:
23
- print("GPU configuration skipped")
 
 
24
 
25
- DR_CLASSES = ["No DR","Mild","Moderate","Severe","Proliferative DR"]
26
- DME_CLASSES = ["No DME","Low Risk","High Risk"]
 
 
 
 
 
 
 
 
 
27
 
28
- # ===== LOAD MODEL (with error handling) =====
29
- MODEL_PATH = "model.keras"
 
 
 
30
 
31
- # Cek apakah model file ada
32
- if not os.path.exists(MODEL_PATH):
33
- # Fallback untuk demo jika model tidak ada
34
- print(f"Warning: {MODEL_PATH} not found. Using mock predictions.")
35
- model = None
36
- else:
37
- try:
38
- # Load dengan opsi yang lebih kompatibel
39
- model = tf.keras.models.load_model(
40
- MODEL_PATH,
41
- compile=False,
42
- safe_mode=False # Untuk compatibility
43
- )
44
- print("Model loaded successfully")
45
- except Exception as e:
46
- print(f"Error loading model: {e}")
47
- model = None
48
 
49
- # ===== PREPROCESS =====
50
- def preprocess(img):
51
- img = img.resize((IMG_SIZE, IMG_SIZE))
52
- # Pastikan 3 channel
 
 
53
  if img.mode != 'RGB':
54
  img = img.convert('RGB')
55
- arr = np.array(img) / 255.0
56
- return np.expand_dims(arr, 0).astype(np.float32)
57
-
58
- # ===== CORE PREDICT (with fallback) =====
59
- def core_predict(img):
60
- # Jika model tidak ada, return mock predictions untuk demo
61
- if model is None:
62
- return {
63
- "dr": {
64
- "label": "No DR",
65
- "confidence": 85.5,
66
- "note": "Mock prediction - model not loaded"
67
- },
68
- "dme": {
69
- "label": "No DME",
70
- "confidence": 90.2,
71
- "note": "Mock prediction - model not loaded"
72
- }
73
- }
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  try:
76
- x = preprocess(img)
77
- preds = model.predict(x, verbose=0) # verbose=0 untuk suppress output
78
 
79
- # Handle different model output formats
80
- if isinstance(preds, dict):
81
- dr = preds.get("dr_head", preds.get("DR", preds.get("output_0")))
82
- dme = preds.get("dme_head", preds.get("DME", preds.get("output_1")))
83
- elif isinstance(preds, list) and len(preds) >= 2:
84
- dr = preds[0]
85
- dme = preds[1]
86
- else:
87
- dr = preds[:, :5] if preds.shape[1] >= 5 else preds
88
- dme = preds[:, 5:] if preds.shape[1] >= 8 else preds
89
-
90
- # Pastikan shape benar
91
- dr = dr[0] if len(dr.shape) > 1 else dr
92
- dme = dme[0] if len(dme.shape) > 1 else dme
93
 
94
- # Apply softmax
95
- dr_probs = tf.nn.softmax(dr).numpy()
96
- dme_probs = tf.nn.softmax(dme).numpy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  return {
99
- "dr": {
100
- "label": DR_CLASSES[int(np.argmax(dr_probs))],
101
- "confidence": float(np.max(dr_probs) * 100)
102
- },
103
- "dme": {
104
- "label": DME_CLASSES[int(np.argmax(dme_probs))],
105
- "confidence": float(np.max(dme_probs) * 100)
 
 
 
 
 
 
 
 
 
106
  }
107
  }
108
 
109
  except Exception as e:
110
  return {
111
- "error": str(e),
112
- "note": "Prediction failed"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- # ===== GRADIO INTERFACE =====
116
- def gradio_predict(img):
117
- if img is None:
118
- return {"error": "No image provided"}
119
- return core_predict(img)
120
-
121
- # Buat Gradio interface dengan tema yang lebih sederhana
122
- demo = gr.Interface(
123
- fn=gradio_predict,
124
- inputs=gr.Image(type="pil", label="Upload Retina Image"),
125
- outputs=gr.JSON(label="Prediction Results"),
126
- title="Diabetic Retinopathy & DME Detection",
127
- description="Upload a retina fundus image to detect Diabetic Retinopathy (DR) and Diabetic Macular Edema (DME)",
128
- examples=[
129
- ["sample1.jpg"], # Pastikan file contoh ada
130
- ["sample2.jpg"]
131
- ] if os.path.exists("sample1.jpg") else None,
132
- allow_flagging="never"
133
- )
134
-
135
- # Untuk Hugging Face, cukup export demo
136
  if __name__ == "__main__":
137
- demo.launch(debug=True)
138
- else:
139
- # Untuk Hugging Face deployment
140
- demo.launch = lambda *args, **kwargs: demo
 
 
 
1
  import tensorflow as tf
2
  import gradio as gr
3
  import numpy as np
 
 
4
  import os
5
+ import warnings
6
+ import io
7
+ import json
8
+ from PIL import Image
9
+ import tempfile
10
 
11
+ warnings.filterwarnings("ignore")
12
 
13
+ CUSTOM_CSS = """
14
+ :root {
15
+ color-scheme: light !important;
16
+ }
17
+ body, .gradio-container {
18
+ background-color: #ffffff !important;
19
+ color: #000000 !important;
20
+ }
21
+ .dark .gradio-container * {
22
+ background-color: #ffffff !important;
23
+ color: #000000 !important;
24
+ }
25
+ """
26
+
27
+ # ============================================================
28
+ # 1. LOAD MODEL (with Hugging Face compatibility)
29
+ # ============================================================
30
+ print("=" * 60)
31
+ print("🚀 LOADING MODEL FOR HUGGING FACE SPACES")
32
+ print("=" * 60)
33
+
34
+ # Cek apakah model ada di root atau folder
35
+ MODEL_PATHS = [
36
+ "model.keras",
37
+ "./model.keras",
38
+ "/tmp/model.keras"
39
+ ]
40
+
41
+ best_model = None
42
+ for model_path in MODEL_PATHS:
43
+ if os.path.exists(model_path):
44
  try:
45
+ print(f"📂 Trying to load model from: {model_path}")
46
+ best_model = tf.keras.models.load_model(
47
+ model_path,
48
+ compile=False,
49
+ safe_mode=False # Important for compatibility
50
+ )
51
+ print(f"✅ Model loaded successfully from {model_path}")
52
+ break
53
+ except Exception as e:
54
+ print(f"❌ Failed to load from {model_path}: {e}")
55
 
56
+ # Jika model tidak ditemukan, buat dummy model
57
+ if best_model is None:
58
+ print("⚠️ No model file found. Creating dummy model for demo...")
59
+ from tensorflow.keras import layers, Model
60
+ inputs = layers.Input(shape=(224, 224, 3))
61
+ x = layers.GlobalAveragePooling2D()(inputs)
62
+ dr_output = layers.Dense(5, name="dr_head")(x)
63
+ dme_output = layers.Dense(3, name="dme_head")(x)
64
+ best_model = Model(inputs, {"dr_head": dr_output, "dme_head": dme_output})
65
+ best_model.compile(optimizer="adam", loss="categorical_crossentropy")
66
+ print("✅ Dummy model created")
67
 
68
+ # Summary model (debug info)
69
+ try:
70
+ best_model.summary()
71
+ except:
72
+ print("ℹ️ Model loaded, summary not available")
73
 
74
+ # ============================================================
75
+ # 2. CONFIG
76
+ # ============================================================
77
+ IMG_SIZE = 224
78
+ DR_CLASSES = ["No DR", "Mild", "Moderate", "Severe", "Proliferative DR"]
79
+ DME_CLASSES = ["No DME", "Low Risk", "High Risk"]
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ # ============================================================
82
+ # 3. PREPROCESSING FUNCTIONS
83
+ # ============================================================
84
+ def preprocess_pil_image(img):
85
+ """Preprocess PIL Image for prediction"""
86
+ # Convert to RGB if needed
87
  if img.mode != 'RGB':
88
  img = img.convert('RGB')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ # Resize
91
+ img = img.resize((IMG_SIZE, IMG_SIZE))
92
+
93
+ # Convert to numpy and normalize
94
+ arr = np.array(img, dtype=np.float32) / 255.0
95
+
96
+ # Add batch dimension
97
+ return np.expand_dims(arr, 0)
98
+
99
+ # ============================================================
100
+ # 4. SOFTMAX SAFETY
101
+ # ============================================================
102
+ def ensure_probability(x):
103
+ x = np.asarray(x, dtype=np.float32)
104
+ # If values don't look like probabilities, apply softmax
105
+ if x.min() < 0 or x.max() > 1.0 or abs(x.sum() - 1.0) > 1e-3:
106
+ x = tf.nn.softmax(x).numpy()
107
+ return x
108
+
109
+ # ============================================================
110
+ # 5. CORE PREDICTION FUNCTION
111
+ # ============================================================
112
+ def predict_image(image):
113
+ """Core prediction function that returns structured data"""
114
  try:
115
+ # Preprocess
116
+ img_tensor = preprocess_pil_image(image)
117
 
118
+ # Predict (disable verbose for cleaner output)
119
+ preds = best_model.predict(img_tensor, verbose=0)
120
+
121
+ # ---- Handle different model output formats ----
122
+ dr_pred = None
123
+ dme_pred = None
 
 
 
 
 
 
 
 
124
 
125
+ if isinstance(preds, dict):
126
+ # Cari key untuk DR dan DME
127
+ dr_keys = [k for k in preds.keys() if 'dr' in k.lower()]
128
+ dme_keys = [k for k in preds.keys() if 'dme' in k.lower()]
129
+
130
+ if dr_keys:
131
+ dr_pred = preds[dr_keys[0]]
132
+ if dme_keys:
133
+ dme_pred = preds[dme_keys[0]]
134
+
135
+ # Jika tidak ketemu, ambil 2 output pertama
136
+ if dr_pred is None and len(preds) >= 2:
137
+ keys = list(preds.keys())
138
+ dr_pred = preds[keys[0]]
139
+ dme_pred = preds[keys[1]]
140
+
141
+ elif isinstance(preds, (list, tuple)):
142
+ if len(preds) >= 2:
143
+ dr_pred = preds[0]
144
+ dme_pred = preds[1]
145
+ else:
146
+ dr_pred = preds[0][:, :5] if len(preds[0].shape) > 1 else preds[0][:5]
147
+ dme_pred = preds[0][:, 5:8] if len(preds[0].shape) > 1 else preds[0][5:8]
148
+
149
+ elif isinstance(preds, np.ndarray):
150
+ if len(preds.shape) == 2:
151
+ dr_pred = preds[:, :5]
152
+ dme_pred = preds[:, 5:8]
153
+ else:
154
+ dr_pred = preds[:5]
155
+ dme_pred = preds[5:8]
156
+
157
+ # Ambil batch pertama jika ada batch dimension
158
+ if dr_pred is not None and len(dr_pred.shape) > 1:
159
+ dr_pred = dr_pred[0]
160
+ if dme_pred is not None and len(dme_pred.shape) > 1:
161
+ dme_pred = dme_pred[0]
162
 
163
+ # Jika masih None, beri nilai default
164
+ if dr_pred is None:
165
+ dr_pred = np.zeros(5)
166
+ if dme_pred is None:
167
+ dme_pred = np.zeros(3)
168
+
169
+ # ---- Apply softmax ----
170
+ dr_probs = ensure_probability(dr_pred)
171
+ dme_probs = ensure_probability(dme_pred)
172
+
173
+ # ---- Get results ----
174
+ dr_idx = int(np.argmax(dr_probs))
175
+ dme_idx = int(np.argmax(dme_probs))
176
+
177
+ dr_name = DR_CLASSES[dr_idx]
178
+ dme_name = DME_CLASSES[dme_idx]
179
+
180
+ dr_conf = float(dr_probs[dr_idx] * 100)
181
+ dme_conf = float(dme_probs[dme_idx] * 100)
182
+
183
+ # ---- Generate recommendations ----
184
+ if dr_name in ["No DR"]:
185
+ rec_dr = "Lanjutkan pola hidup sehat dan lakukan pemeriksaan mata rutin minimal 1 tahun sekali."
186
+ elif dr_name in ["Mild", "Moderate"]:
187
+ rec_dr = "Disarankan kontrol gula darah secara ketat dan pemeriksaan mata berkala setiap 6 bulan."
188
+ else: # Severe / Proliferative
189
+ rec_dr = "Disarankan segera konsultasi ke dokter spesialis mata untuk evaluasi dan penanganan lebih lanjut."
190
+
191
+ if dme_name == "No DME":
192
+ rec_dme = "Belum ditemukan tanda edema makula diabetik, lanjutkan pemantauan rutin."
193
+ elif dme_name == "Low Risk":
194
+ rec_dme = "Perlu observasi ketat dan pemeriksaan lanjutan untuk mencegah progresivitas."
195
+ else: # High Risk
196
+ rec_dme = "Disarankan segera mendapatkan evaluasi klinis dan terapi oleh dokter spesialis mata."
197
+
198
+ # Return both structured data and HTML
199
  return {
200
+ "success": True,
201
+ "predictions": {
202
+ "diabetic_retinopathy": {
203
+ "classification": dr_name,
204
+ "confidence": dr_conf,
205
+ "index": dr_idx,
206
+ "probabilities": dr_probs.tolist(),
207
+ "recommendation": rec_dr
208
+ },
209
+ "diabetic_macular_edema": {
210
+ "classification": dme_name,
211
+ "confidence": dme_conf,
212
+ "index": dme_idx,
213
+ "probabilities": dme_probs.tolist(),
214
+ "recommendation": rec_dme
215
+ }
216
  }
217
  }
218
 
219
  except Exception as e:
220
  return {
221
+ "success": False,
222
+ "error": str(e)
223
+ }
224
+
225
+ # ============================================================
226
+ # 6. API ENDPOINT FUNCTION (for Hugging Face)
227
+ # ============================================================
228
+ def api_predict(image):
229
+ """Function for API endpoint"""
230
+ return predict_image(image)
231
+
232
+ # ============================================================
233
+ # 7. FORMAT OUTPUT FOR GRADIO
234
+ # ============================================================
235
+ def format_prediction_html(result):
236
+ """Format prediction result as HTML for Gradio"""
237
+ if not result["success"]:
238
+ return f"""
239
+ <div style="color: red; padding: 20px; border: 2px solid red; border-radius: 10px;">
240
+ <h3>❌ Error</h3>
241
+ <p>{result['error']}</p>
242
+ </div>
243
+ """
244
+
245
+ preds = result["predictions"]
246
+ dr = preds["diabetic_retinopathy"]
247
+ dme = preds["diabetic_macular_edema"]
248
+
249
+ # Warna berdasarkan severity
250
+ dr_color = {
251
+ "No DR": "#28a745",
252
+ "Mild": "#ffc107",
253
+ "Moderate": "#fd7e14",
254
+ "Severe": "#dc3545",
255
+ "Proliferative DR": "#6f42c1"
256
+ }.get(dr["classification"], "#000000")
257
+
258
+ dme_color = {
259
+ "No DME": "#28a745",
260
+ "Low Risk": "#ffc107",
261
+ "High Risk": "#dc3545"
262
+ }.get(dme["classification"], "#000000")
263
+
264
+ html = f"""
265
+ <div style="font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto;">
266
+
267
+ <!-- Header -->
268
+ <div style="text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
269
+ color: white; padding: 25px; border-radius: 15px 15px 0 0; margin-bottom: 20px;">
270
+ <h1 style="margin: 0; font-size: 32px;">🔬 HASIL DETEKSI</h1>
271
+ <p style="margin: 5px 0 0 0; font-size: 16px; opacity: 0.9;">AI-Powered Retina Analysis</p>
272
+ </div>
273
+
274
+ <!-- Results Table -->
275
+ <div style="background: white; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;">
276
+ <table style="width: 100%; border-collapse: collapse;">
277
+ <thead>
278
+ <tr style="background-color: #f8f9fa;">
279
+ <th style="padding: 16px; text-align: left; border-bottom: 2px solid #dee2e6; font-size: 18px;">Kondisi</th>
280
+ <th style="padding: 16px; text-align: left; border-bottom: 2px solid #dee2e6; font-size: 18px;">Klasifikasi</th>
281
+ <th style="padding: 16px; text-align: left; border-bottom: 2px solid #dee2e6; font-size: 18px;">Tingkat Kepercayaan</th>
282
+ </tr>
283
+ </thead>
284
+ <tbody>
285
+ <tr>
286
+ <td style="padding: 16px; border-bottom: 1px solid #dee2e6; font-weight: bold;">Diabetic Retinopathy (DR)</td>
287
+ <td style="padding: 16px; border-bottom: 1px solid #dee2e6;">
288
+ <span style="color: {dr_color}; font-weight: bold; font-size: 18px;">{dr['classification']}</span>
289
+ </td>
290
+ <td style="padding: 16px; border-bottom: 1px solid #dee2e6;">
291
+ <div style="display: flex; align-items: center; gap: 10px;">
292
+ <div style="flex-grow: 1; background: #e9ecef; height: 20px; border-radius: 10px; overflow: hidden;">
293
+ <div style="width: {dr['confidence']}%; background: {dr_color}; height: 100%;"></div>
294
+ </div>
295
+ <span style="font-weight: bold; min-width: 60px;">{dr['confidence']:.1f}%</span>
296
+ </div>
297
+ </td>
298
+ </tr>
299
+ <tr>
300
+ <td style="padding: 16px; border-bottom: 1px solid #dee2e6; font-weight: bold;">Diabetic Macular Edema (DME)</td>
301
+ <td style="padding: 16px; border-bottom: 1px solid #dee2e6;">
302
+ <span style="color: {dme_color}; font-weight: bold; font-size: 18px;">{dme['classification']}</span>
303
+ </td>
304
+ <td style="padding: 16px; border-bottom: 1px solid #dee2e6;">
305
+ <div style="display: flex; align-items: center; gap: 10px;">
306
+ <div style="flex-grow: 1; background: #e9ecef; height: 20px; border-radius: 10px; overflow: hidden;">
307
+ <div style="width: {dme['confidence']}%; background: {dme_color}; height: 100%;"></div>
308
+ </div>
309
+ <span style="font-weight: bold; min-width: 60px;">{dme['confidence']:.1f}%</span>
310
+ </div>
311
+ </td>
312
+ </tr>
313
+ </tbody>
314
+ </table>
315
+ </div>
316
+
317
+ <!-- Recommendations -->
318
+ <div style="margin-top: 25px; background: white; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;">
319
+ <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 15px;">
320
+ <h3 style="margin: 0; font-size: 22px;">🩺 REKOMENDASI KLINIS</h3>
321
+ </div>
322
+ <div style="padding: 20px;">
323
+ <div style="margin-bottom: 15px;">
324
+ <h4 style="color: #333; margin-bottom: 8px;">• Diabetic Retinopathy (DR):</h4>
325
+ <p style="margin: 0; color: #555; line-height: 1.6;">{dr['recommendation']}</p>
326
+ </div>
327
+ <div>
328
+ <h4 style="color: #333; margin-bottom: 8px;">• Diabetic Macular Edema (DME):</h4>
329
+ <p style="margin: 0; color: #555; line-height: 1.6;">{dme['recommendation']}</p>
330
+ </div>
331
+ </div>
332
+ </div>
333
+
334
+ <!-- Disclaimer -->
335
+ <div style="margin-top: 20px; padding: 15px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; font-size: 14px;">
336
+ <strong>⚠️ Disclaimer:</strong> Hasil ini merupakan prediksi AI dan bukan diagnosis medis. Konsultasikan dengan dokter spesialis mata untuk diagnosis yang akurat.
337
+ </div>
338
+
339
+ </div>
340
+ """
341
+
342
+ return html
343
+
344
+ # ============================================================
345
+ # 8. GRADIO INTERFACE
346
+ # ============================================================
347
+ def gradio_predict(image):
348
+ """Main function for Gradio interface"""
349
+ if image is None:
350
+ return "❌ Silakan unggah gambar fundus retina"
351
+
352
+ # Get prediction
353
+ result = predict_image(image)
354
+
355
+ # Format as HTML
356
+ return format_prediction_html(result)
357
+
358
+ # ============================================================
359
+ # 9. PREPARE EXAMPLE IMAGES
360
+ # ============================================================
361
+ def get_example_images():
362
+ """Get example images for demo"""
363
+ example_images = []
364
+
365
+ # Common retina image filenames to check
366
+ possible_files = [
367
+ "sample.jpg", "sample.png", "example.jpg", "example.png",
368
+ "test.jpg", "test.png", "retina.jpg", "retina.png",
369
+ "IDRiD_001.jpg", "IDRiD_002.jpg", "IDRiD_003.jpg",
370
+ "image1.jpg", "image2.jpg", "image3.jpg"
371
+ ]
372
+
373
+ # Check current directory and subdirectories
374
+ for root, dirs, files in os.walk("."):
375
+ for file in files:
376
+ if file.lower().endswith(('.jpg', '.jpeg', '.png')):
377
+ # Skip very large files
378
+ filepath = os.path.join(root, file)
379
+ if os.path.getsize(filepath) < 5 * 1024 * 1024: # 5MB limit
380
+ example_images.append([filepath])
381
+ if len(example_images) >= 8: # Max 8 examples
382
+ break
383
+
384
+ return example_images[:8] # Return max 8 examples
385
+
386
+ # ============================================================
387
+ # 10. CREATE GRADIO APP
388
+ # ============================================================
389
+ with gr.Blocks(
390
+ title="DR & DME Detection",
391
+ css=CUSTOM_CSS,
392
+ theme=gr.themes.Soft()
393
+ ) as demo:
394
+
395
+ # Header
396
+ gr.Markdown("""
397
+ # 🩺 DETEKSI DIABETIC RETINOPATHY & DME
398
+ ### Sistem AI untuk Analisis Citra Fundus Retina
399
+
400
+ Upload gambar fundus retina untuk mendeteksi:
401
+ - **Diabetic Retinopathy (DR)**: Kerusakan retina akibat diabetes
402
+ - **Diabetic Macular Edema (DME)**: Pembengkakan di makula
403
+ """)
404
+
405
+ with gr.Row():
406
+ with gr.Column(scale=1):
407
+ # Upload section
408
+ image_input = gr.Image(
409
+ type="pil",
410
+ label="📤 Upload Gambar Retina",
411
+ height=300
412
+ )
413
+
414
+ upload_btn = gr.Button(
415
+ "🔍 Analisis Gambar",
416
+ variant="primary",
417
+ size="lg"
418
+ )
419
+
420
+ gr.Markdown("""
421
+ **Format yang didukung:** JPG, PNG, JPEG
422
+ **Ukuran rekomendasi:** 224×224 piksel
423
+ **Warna:** RGB (akan dikonversi otomatis)
424
+ """)
425
+
426
+ with gr.Column(scale=2):
427
+ # Results section
428
+ output_html = gr.HTML(
429
+ label="📊 Hasil Analisis",
430
+ value="<div style='text-align: center; padding: 50px; color: #666;'>Hasil analisis akan muncul di sini setelah mengupload gambar.</div>"
431
+ )
432
+
433
+ # Examples section
434
+ example_images = get_example_images()
435
+ if example_images:
436
+ gr.Markdown("### 🧪 Contoh Gambar (Klik untuk mencoba)")
437
+ gr.Examples(
438
+ examples=example_images,
439
+ inputs=image_input,
440
+ outputs=output_html,
441
+ fn=gradio_predict,
442
+ cache_examples=False # Set True for faster example loading
443
+ )
444
+
445
+ # API Info section (for mobile access)
446
+ gr.Markdown("---")
447
+ with gr.Accordion("📱 Akses dari Mobile App", open=False):
448
+ gr.Markdown("""
449
+ ### API Endpoint untuk Mobile
450
+
451
+ **URL:** `https://[your-huggingface-space].hf.space/run/predict`
452
+
453
+ **Method:** POST
454
+
455
+ **Content-Type:** multipart/form-data
456
+
457
+ **Body:**
458
+ ```json
459
+ {
460
+ "data": [image_data]
461
  }
462
+ ```
463
+
464
+ **Contoh cURL:**
465
+ ```bash
466
+ curl -X POST https://[your-space].hf.space/run/predict \\
467
+ -F "data=@retina_image.jpg"
468
+ ```
469
+
470
+ **Response Format:**
471
+ ```json
472
+ {{
473
+ "success": true,
474
+ "predictions": {{
475
+ "diabetic_retinopathy": {{...}},
476
+ "diabetic_macular_edema": {{...}}
477
+ }}
478
+ }}
479
+ ```
480
+ """)
481
+
482
+ # Connect button to function
483
+ upload_btn.click(
484
+ fn=gradio_predict,
485
+ inputs=image_input,
486
+ outputs=output_html
487
+ )
488
+
489
+ # Also trigger on image upload
490
+ image_input.change(
491
+ fn=gradio_predict,
492
+ inputs=image_input,
493
+ outputs=output_html
494
+ )
495
 
496
+ # ============================================================
497
+ # 11. FOR HUGGING FACE DEPLOYMENT
498
+ # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  if __name__ == "__main__":
500
+ # Launch for Hugging Face Spaces
501
+ demo.launch(
502
+ debug=False,
503
+ show_error=True,
504
+ share=False # Set to True if you want a public link
505
+ )