kodetr commited on
Commit
2f7df2e
Β·
verified Β·
1 Parent(s): 3ed806b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -188
app.py CHANGED
@@ -8,6 +8,10 @@ import json
8
  import base64
9
  from PIL import Image
10
  import tempfile
 
 
 
 
11
 
12
  warnings.filterwarnings("ignore")
13
 
@@ -66,7 +70,25 @@ DR_CLASSES = ["No DR", "Mild", "Moderate", "Severe", "Proliferative DR"]
66
  DME_CLASSES = ["No DME", "Low Risk", "High Risk"]
67
 
68
  # ============================================================
69
- # 3. PREPROCESSING FUNCTIONS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  # ============================================================
71
  def preprocess_pil_image(img):
72
  """Preprocess PIL Image for prediction"""
@@ -84,7 +106,7 @@ def preprocess_pil_image(img):
84
  return np.expand_dims(arr, 0)
85
 
86
  # ============================================================
87
- # 4. SOFTMAX SAFETY
88
  # ============================================================
89
  def ensure_probability(x):
90
  x = np.asarray(x, dtype=np.float32)
@@ -94,7 +116,7 @@ def ensure_probability(x):
94
  return x
95
 
96
  # ============================================================
97
- # 5. CORE PREDICTION FUNCTION
98
  # ============================================================
99
  def predict_image(image):
100
  """Core prediction function that returns structured data"""
@@ -182,7 +204,6 @@ def predict_image(image):
182
  else: # High Risk
183
  rec_dme = "Disarankan segera mendapatkan evaluasi klinis dan terapi oleh dokter spesialis mata."
184
 
185
- # Return both structured data and HTML
186
  return {
187
  "success": True,
188
  "predictions": {
@@ -210,38 +231,50 @@ def predict_image(image):
210
  }
211
 
212
  # ============================================================
213
- # 6. API PREDICT FUNCTION UNTUK /run/predict
214
  # ============================================================
215
- def api_predict(image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  """
217
- Function untuk API endpoint /run/predict
218
- Gradio akan secara otomatis mengkonversi input ke format yang tepat
219
  """
220
  try:
221
- if image is None:
222
- return {"error": "No image provided"}
223
-
224
- # Jika input adalah dictionary (dari JSON API call)
225
- if isinstance(image, dict):
226
- if "data" in image:
227
- # Handle base64 dari JSON API
228
- return handle_api_json_input(image)
229
 
230
- # Jika input adalah file/bytes (dari form-data)
231
- # Gradio sudah otomatis konversi ke PIL Image
232
- result = predict_image(image)
233
- return result
234
 
235
- except Exception as e:
236
- return {
237
- "success": False,
238
- "error": f"API processing error: {str(e)}"
239
- }
240
-
241
- def handle_api_json_input(image_data):
242
- """Handle JSON input dengan base64"""
243
- try:
244
- img_data = image_data["data"]
245
  if isinstance(img_data, list):
246
  img_data = img_data[0]
247
 
@@ -254,16 +287,48 @@ def handle_api_json_input(image_data):
254
  img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
255
 
256
  # Get prediction
257
- return predict_image(img)
 
 
 
258
 
 
 
 
 
259
  except Exception as e:
260
- return {
261
- "success": False,
262
- "error": f"Base64 processing error: {str(e)}"
263
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
  # ============================================================
266
- # 7. FORMAT OUTPUT FOR GRADIO UI
267
  # ============================================================
268
  def format_prediction_html(result):
269
  """Format prediction result as HTML for Gradio"""
@@ -374,9 +439,6 @@ def format_prediction_html(result):
374
 
375
  return html
376
 
377
- # ============================================================
378
- # 8. GRADIO UI FUNCTION
379
- # ============================================================
380
  def gradio_predict(image):
381
  """Main function for Gradio UI"""
382
  if image is None:
@@ -389,54 +451,30 @@ def gradio_predict(image):
389
  return format_prediction_html(result)
390
 
391
  # ============================================================
392
- # 9. CREATE GRADIO INTERFACES
393
- # ============================================================
394
-
395
- # Interface untuk Web UI
396
- web_interface = gr.Interface(
397
- fn=gradio_predict,
398
- inputs=gr.Image(type="pil", label="πŸ“€ Upload Gambar Retina"),
399
- outputs=gr.HTML(label="πŸ“Š Hasil Analisis"),
400
- title="🩺 DETEKSI DIABETIC RETINOPATHY & DME",
401
- description="Sistem AI untuk Analisis Citra Fundus Retina",
402
- allow_flagging="never"
403
- )
404
-
405
- # Interface untuk API (akan digunakan oleh /run/predict)
406
- api_interface = gr.Interface(
407
- fn=api_predict,
408
- inputs=gr.Image(type="pil"),
409
- outputs=gr.JSON(),
410
- title="API Endpoint",
411
- description="Use this endpoint for API calls",
412
- allow_flagging="never"
413
- )
414
-
415
- # ============================================================
416
- # 10. MULTI TEST IMAGES
417
  # ============================================================
418
- TEST_IMAGES = [
419
- "IDRiD_001test.jpg",
420
- "IDRiD_004test.jpg",
421
- "IDRiD_005test.jpg",
422
- "IDRiD_006test.jpg",
423
- "IDRiD_007test.jpg",
424
- "IDRiD_008test.jpg",
425
- "IDRiD_009test.jpg",
426
- "IDRiD_010test.jpg",
427
- "IDRiD_011test.jpg",
428
- "IDRiD_012test.jpg",
429
- ]
430
-
431
- TEST_IMAGES = [[p] for p in TEST_IMAGES if os.path.exists(p)]
432
-
 
433
 
434
  # ============================================================
435
- # 11. CREATE GRADIO APP WITH BLOCKS
436
  # ============================================================
437
  with gr.Blocks(
438
  title="DR & DME Detection",
439
- # css=CUSTOM_CSS,
440
  theme=gr.themes.Soft()
441
  ) as demo:
442
 
@@ -450,123 +488,112 @@ with gr.Blocks(
450
  - **Diabetic Macular Edema (DME)**: Pembengkakan di makula
451
  """)
452
 
453
- # Create tabs for Web UI and API
454
- with gr.Tabs():
455
- # Tab 1: Web UI
456
- with gr.TabItem("🌐 Web Interface"):
457
- with gr.Row():
458
- with gr.Column(scale=1):
459
- # Upload section
460
- image_input = gr.Image(
461
- type="pil",
462
- label="πŸ“€ Upload Gambar Retina",
463
- height=300
464
- )
465
-
466
- upload_btn = gr.Button(
467
- "πŸ” Analisis Gambar",
468
- variant="primary",
469
- size="lg"
470
- )
471
-
472
- gr.Markdown("""
473
- **Format yang didukung:** JPG, PNG, JPEG
474
- **Ukuran rekomendasi:** 224Γ—224 piksel
475
- **Warna:** RGB (akan dikonversi otomatis)
476
- """)
477
-
478
- with gr.Column(scale=2):
479
- # Results section
480
- output_html = gr.HTML(
481
- label="πŸ“Š Hasil Analisis",
482
- value="<div style='text-align: center; padding: 50px; color: #666;'>Hasil analisis akan muncul di sini setelah mengupload gambar.</div>"
483
- )
484
-
485
-
486
- gr.Markdown("### πŸ§ͺ Data Testing")
487
- gr.Examples(
488
- examples=TEST_IMAGES,
489
- inputs=image_input
490
  )
491
 
492
- # Connect button to function
493
- upload_btn.click(
494
- fn=gradio_predict,
495
- inputs=image_input,
496
- outputs=output_html
497
  )
498
 
499
- # Also trigger on image upload
500
- image_input.change(
501
- fn=gradio_predict,
502
- inputs=image_input,
503
- outputs=output_html
504
- )
505
-
506
- # Tab 2: API Interface
507
- with gr.TabItem("πŸ”§ API Endpoint"):
508
  gr.Markdown("""
509
- ### API Endpoint untuk Mobile App
510
-
511
- **URL:** `/run/predict`
512
-
513
- **Method:** POST
514
-
515
- **Content-Type:** `multipart/form-data` atau `application/json`
516
  """)
517
-
518
- with gr.Row():
519
- with gr.Column():
520
- api_image_input = gr.Image(
521
- type="pil",
522
- label="Test API dengan gambar"
523
- )
524
- api_test_btn = gr.Button("Test API", variant="secondary")
525
-
526
- with gr.Column():
527
- api_output = gr.JSON(
528
- label="API Response",
529
- value={"info": "API response akan muncul di sini"}
530
- )
531
-
532
- # Connect API test button
533
- api_test_btn.click(
534
- fn=api_predict,
535
- inputs=api_image_input,
536
- outputs=api_output
537
  )
538
-
539
- gr.Markdown("""
540
- ### πŸ“‹ Contoh Penggunaan API
541
-
542
- **cURL dengan file:**
543
- ```bash
544
- curl -X POST "https://[your-space].hf.space/run/predict" \\
545
- -F "data=@retina_image.jpg"
546
- ```
547
-
548
- **Python:**
549
- ```python
550
- import requests
551
-
552
- with open("retina_image.jpg", "rb") as f:
553
- response = requests.post(
554
- "https://[your-space].hf.space/run/predict",
555
- files={"data": f}
556
- )
557
- print(response.json())
558
- ```
559
- """)
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
 
562
  # ============================================================
563
- # 11. LAUNCH FOR HUGGING FACE
564
  # ============================================================
565
  if __name__ == "__main__":
566
- # Launch untuk Hugging Face Spaces
567
- demo.launch(
568
- server_name="0.0.0.0",
569
- server_port=7860,
570
- share=False,
571
- debug=False
 
 
 
 
 
 
 
 
572
  )
 
8
  import base64
9
  from PIL import Image
10
  import tempfile
11
+ from fastapi import FastAPI, File, UploadFile, HTTPException
12
+ from fastapi.responses import JSONResponse, HTMLResponse
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ import uvicorn
15
 
16
  warnings.filterwarnings("ignore")
17
 
 
70
  DME_CLASSES = ["No DME", "Low Risk", "High Risk"]
71
 
72
  # ============================================================
73
+ # 3. CREATE FASTAPI APP
74
+ # ============================================================
75
+ app = FastAPI(
76
+ title="DR & DME Detection API",
77
+ description="API untuk mendeteksi Diabetic Retinopathy dan Diabetic Macular Edema dari citra retina",
78
+ version="1.0.0"
79
+ )
80
+
81
+ # Enable CORS for mobile access
82
+ app.add_middleware(
83
+ CORSMiddleware,
84
+ allow_origins=["*"],
85
+ allow_credentials=True,
86
+ allow_methods=["*"],
87
+ allow_headers=["*"],
88
+ )
89
+
90
+ # ============================================================
91
+ # 4. PREPROCESSING FUNCTIONS
92
  # ============================================================
93
  def preprocess_pil_image(img):
94
  """Preprocess PIL Image for prediction"""
 
106
  return np.expand_dims(arr, 0)
107
 
108
  # ============================================================
109
+ # 5. SOFTMAX SAFETY
110
  # ============================================================
111
  def ensure_probability(x):
112
  x = np.asarray(x, dtype=np.float32)
 
116
  return x
117
 
118
  # ============================================================
119
+ # 6. CORE PREDICTION FUNCTION
120
  # ============================================================
121
  def predict_image(image):
122
  """Core prediction function that returns structured data"""
 
204
  else: # High Risk
205
  rec_dme = "Disarankan segera mendapatkan evaluasi klinis dan terapi oleh dokter spesialis mata."
206
 
 
207
  return {
208
  "success": True,
209
  "predictions": {
 
231
  }
232
 
233
  # ============================================================
234
+ # 7. FASTAPI ENDPOINTS
235
  # ============================================================
236
+ @app.get("/")
237
+ async def root():
238
+ """Root endpoint"""
239
+ return {
240
+ "message": "DR & DME Detection API",
241
+ "version": "1.0.0",
242
+ "endpoints": {
243
+ "docs": "/docs",
244
+ "health": "/health",
245
+ "predict": "/predict",
246
+ "predict_form": "/predict_form",
247
+ "gradio_ui": "/gradio"
248
+ }
249
+ }
250
+
251
+ @app.get("/health")
252
+ async def health_check():
253
+ """Health check endpoint"""
254
+ return {
255
+ "status": "healthy",
256
+ "model_loaded": best_model is not None,
257
+ "timestamp": tf.timestamp().numpy().astype(float)
258
+ }
259
+
260
+ @app.post("/predict")
261
+ async def predict_endpoint(file: UploadFile = File(...)):
262
  """
263
+ Predict endpoint for JSON base64 input
264
+ Accepts: JSON with base64 image
265
  """
266
  try:
267
+ # Read and parse JSON
268
+ content = await file.read()
269
+ try:
270
+ data = json.loads(content)
271
+ except:
272
+ raise HTTPException(status_code=400, detail="Invalid JSON format")
 
 
273
 
274
+ if "data" not in data:
275
+ raise HTTPException(status_code=400, detail="Missing 'data' field in JSON")
 
 
276
 
277
+ img_data = data["data"]
 
 
 
 
 
 
 
 
 
278
  if isinstance(img_data, list):
279
  img_data = img_data[0]
280
 
 
287
  img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
288
 
289
  # Get prediction
290
+ result = predict_image(img)
291
+
292
+ if not result["success"]:
293
+ raise HTTPException(status_code=500, detail=result["error"])
294
 
295
+ return JSONResponse(content=result)
296
+
297
+ except HTTPException:
298
+ raise
299
  except Exception as e:
300
+ raise HTTPException(status_code=500, detail=str(e))
301
+
302
+ @app.post("/predict_form")
303
+ async def predict_form_endpoint(file: UploadFile = File(...)):
304
+ """
305
+ Predict endpoint for form-data file upload
306
+ Accepts: image file (jpg, png, jpeg)
307
+ """
308
+ try:
309
+ # Check file type
310
+ if not file.content_type.startswith('image/'):
311
+ raise HTTPException(status_code=400, detail="File must be an image")
312
+
313
+ # Read image
314
+ contents = await file.read()
315
+ img = Image.open(io.BytesIO(contents)).convert("RGB")
316
+
317
+ # Get prediction
318
+ result = predict_image(img)
319
+
320
+ if not result["success"]:
321
+ raise HTTPException(status_code=500, detail=result["error"])
322
+
323
+ return JSONResponse(content=result)
324
+
325
+ except HTTPException:
326
+ raise
327
+ except Exception as e:
328
+ raise HTTPException(status_code=500, detail=str(e))
329
 
330
  # ============================================================
331
+ # 8. GRADIO UI FUNCTIONS (for Web Interface)
332
  # ============================================================
333
  def format_prediction_html(result):
334
  """Format prediction result as HTML for Gradio"""
 
439
 
440
  return html
441
 
 
 
 
442
  def gradio_predict(image):
443
  """Main function for Gradio UI"""
444
  if image is None:
 
451
  return format_prediction_html(result)
452
 
453
  # ============================================================
454
+ # 9. PREPARE EXAMPLE IMAGES
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  # ============================================================
456
+ def get_example_images():
457
+ """Get example images for demo"""
458
+ example_images = []
459
+
460
+ # Check current directory and subdirectories
461
+ for root, dirs, files in os.walk("."):
462
+ for file in files:
463
+ if file.lower().endswith(('.jpg', '.jpeg', '.png')):
464
+ # Skip very large files
465
+ filepath = os.path.join(root, file)
466
+ if os.path.getsize(filepath) < 5 * 1024 * 1024: # 5MB limit
467
+ example_images.append([filepath])
468
+ if len(example_images) >= 8: # Max 8 examples
469
+ break
470
+
471
+ return example_images[:8] # Return max 8 examples
472
 
473
  # ============================================================
474
+ # 10. CREATE GRADIO APP
475
  # ============================================================
476
  with gr.Blocks(
477
  title="DR & DME Detection",
 
478
  theme=gr.themes.Soft()
479
  ) as demo:
480
 
 
488
  - **Diabetic Macular Edema (DME)**: Pembengkakan di makula
489
  """)
490
 
491
+ with gr.Row():
492
+ with gr.Column(scale=1):
493
+ # Upload section
494
+ image_input = gr.Image(
495
+ type="pil",
496
+ label="πŸ“€ Upload Gambar Retina",
497
+ height=300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  )
499
 
500
+ upload_btn = gr.Button(
501
+ "πŸ” Analisis Gambar",
502
+ variant="primary",
503
+ size="lg"
 
504
  )
505
 
 
 
 
 
 
 
 
 
 
506
  gr.Markdown("""
507
+ **Format yang didukung:** JPG, PNG, JPEG
508
+ **Ukuran rekomendasi:** 224Γ—224 piksel
509
+ **Warna:** RGB (akan dikonversi otomatis)
 
 
 
 
510
  """)
511
+
512
+ with gr.Column(scale=2):
513
+ # Results section
514
+ output_html = gr.HTML(
515
+ label="πŸ“Š Hasil Analisis",
516
+ value="<div style='text-align: center; padding: 50px; color: #666;'>Hasil analisis akan muncul di sini setelah mengupload gambar.</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
 
519
+ # Examples section
520
+ example_images = get_example_images()
521
+ if example_images:
522
+ gr.Markdown("### πŸ§ͺ Contoh Gambar (Klik untuk mencoba)")
523
+ gr.Examples(
524
+ examples=example_images,
525
+ inputs=image_input,
526
+ outputs=output_html,
527
+ fn=gradio_predict,
528
+ cache_examples=False
529
+ )
530
+
531
+ # API Info section
532
+ gr.Markdown("---")
533
+ with gr.Accordion("πŸ“± Akses API dari Mobile App", open=False):
534
+ gr.Markdown("""
535
+ ### API Endpoints:
536
+
537
+ 1. **POST /predict_form** - Upload file gambar
538
+ ```bash
539
+ curl -X POST "https://kodetr-idrid.hf.space/predict_form" \\
540
+ -F "file=@retina_image.jpg"
541
+ ```
542
+
543
+ 2. **POST /predict** - JSON dengan base64
544
+ ```json
545
+ {
546
+ "data": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
547
+ }
548
+ ```
549
+
550
+ 3. **GET /health** - Health check
551
+ 4. **GET /** - API info
552
+ """)
553
+
554
+ # Connect button to function
555
+ upload_btn.click(
556
+ fn=gradio_predict,
557
+ inputs=image_input,
558
+ outputs=output_html
559
+ )
560
+
561
+ # ============================================================
562
+ # 11. MOUNT GRADIO TO FASTAPI
563
+ # ============================================================
564
+ @app.get("/gradio")
565
+ async def gradio_interface():
566
+ """Redirect to Gradio interface"""
567
+ return HTMLResponse("""
568
+ <html>
569
+ <head>
570
+ <meta http-equiv="refresh" content="0; url=/gradio/" />
571
+ </head>
572
+ <body>
573
+ <p>Redirecting to Gradio interface...</p>
574
+ </body>
575
+ </html>
576
+ """)
577
+
578
+ # Mount Gradio app to FastAPI
579
+ app = gr.mount_gradio_app(app, demo, path="/gradio")
580
 
581
  # ============================================================
582
+ # 12. MAIN ENTRY POINT
583
  # ============================================================
584
  if __name__ == "__main__":
585
+ print("\n" + "="*60)
586
+ print("πŸš€ SERVER STARTING")
587
+ print("="*60)
588
+ print(f"πŸ“± FastAPI: http://0.0.0.0:7860")
589
+ print(f"πŸ“Š API Docs: http://0.0.0.0:7860/docs")
590
+ print(f"πŸ–₯️ Gradio UI: http://0.0.0.0:7860/gradio")
591
+ print(f"πŸ₯ Health Check: http://0.0.0.0:7860/health")
592
+ print("="*60)
593
+
594
+ uvicorn.run(
595
+ app,
596
+ host="0.0.0.0",
597
+ port=7860,
598
+ log_level="info"
599
  )