Files changed (1) hide show
  1. app.py +90 -99
app.py CHANGED
@@ -1,112 +1,103 @@
1
- from fastapi import FastAPI, File, UploadFile, HTTPException
2
- import tensorflow as tf
3
- import numpy as np
4
  from PIL import Image
5
  import io
6
  import sys
 
7
  from tensorflow.keras.applications.densenet import preprocess_input
8
 
9
- # 1. Inisialisasi Aplikasi
10
- app = FastAPI(title="Ashoka Hipospadia Classifier API")
 
 
11
 
 
12
  # 2. Load Model
13
- print("Sedang memuat model...")
 
14
  try:
15
- model = tf.keras.models.load_model('cnn_kfold_best_model_v2.h5')
16
- print("Model berhasil dimuat!")
17
  except Exception as e:
18
- print(f"Error memuat model: {e}")
19
- sys.exit(1) # Matikan server jika model gagal load
20
-
21
- # Label kelas: 0 = normal, 1 = buried
22
- class_names = ['normal', 'buried']
23
-
24
- # 3. Fungsi Preprocessing
25
- def prepare_image(image_bytes):
26
- """
27
- Preprocessing gambar untuk model Densenet
28
- - Konversi ke RGB (3 channel)
29
- - Resize ke 224x224
30
- - Preprocessing ResNet50
31
- """
32
- try:
33
- img = Image.open(io.BytesIO(image_bytes))
34
-
35
- # Paksa ubah ke RGB agar PNG transparan tidak error
36
- img = img.convert("RGB")
37
-
38
- # Resize ke ukuran input model (224x224 untuk ResNet50)
39
- img = img.resize((224, 224))
40
-
41
- # Convert ke numpy array
42
- img_array = np.array(img)
43
-
44
- # Tambah batch dimension
45
- img_array = np.expand_dims(img_array, axis=0)
46
-
47
- # Preprocessing ResNet50 (HARUS sama dengan training!)
48
- img_array = preprocess_input(img_array)
49
-
50
- return img_array
51
- except Exception as e:
52
- print(f"Error saat memproses gambar: {e}")
53
- return None
54
-
55
- # 4. Endpoint Prediksi
56
  @app.post("/predict")
57
- async def predict(file: UploadFile = File(...)):
58
- """
59
- Endpoint untuk prediksi gambar
60
- Input: File gambar (JPG, PNG, BMP)
61
- Output: JSON dengan class dan confidence
62
- """
63
- try:
64
- # Baca file gambar
65
- image_bytes = await file.read()
66
-
67
- # Proses gambar
68
- processed_image = prepare_image(image_bytes)
69
-
70
- if processed_image is None:
71
- raise HTTPException(status_code=400, detail="File bukan gambar yang valid")
72
-
73
- # Prediksi
74
- prediction = model.predict(processed_image)
75
- pred_value = float(prediction[0][0])
76
-
77
- # Hitung probabilitas
78
- # Model output: 0 = normal, 1 = buried
79
- prob_normal = (1 - pred_value) * 100
80
- prob_buried = pred_value * 100
81
-
82
- # Tentukan kelas berdasarkan threshold 0.5
83
- top_class_idx = 1 if pred_value > 0.5 else 0
84
-
85
- # Hasil dalam format JSON
86
- result = {
87
- "class": class_names[top_class_idx],
88
- "confidence": float(max(prob_normal, prob_buried)),
89
- "probabilities": {
90
- "normal": float(prob_normal),
91
- "buried": float(prob_buried)
92
- }
93
- }
94
- return result
95
-
96
- except Exception as e:
97
- # Cetak error ke log
98
- print(f"CRITICAL ERROR: {e}")
99
- raise HTTPException(status_code=500, detail=str(e))
100
-
101
- # 5. Endpoint Home
102
- @app.get("/")
103
- def home():
104
- """Endpoint root untuk testing API"""
105
  return {
106
- "message": "Ashoka Hipospadia Classifier API Online! 🚀",
107
- "model": "Densenet Binary Classification",
108
- "classes": class_names
 
 
109
  }
110
 
111
- # API siap digunakan dengan uvicorn
112
- # Jalankan dengan: uvicorn app:app --host 0.0.0.0 --port 7860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from PIL import Image
2
  import io
3
  import sys
4
+ import gradio as gr
5
  from tensorflow.keras.applications.densenet import preprocess_input
6
 
7
+ # =========================
8
+ # 1. FastAPI Init
9
+ # =========================
10
+ app = FastAPI(title="Ashoka Buried Penis Classifier API")
11
 
12
+ # =========================
13
  # 2. Load Model
14
+ # =========================
15
+ print("Loading model...")
16
  try:
17
+ model = tf.keras.models.load_model("cnn_kfold_best_model_v2.h5")
18
+ print("Model loaded successfully")
19
  except Exception as e:
20
+ print("Failed to load model:", e)
21
+ sys.exit(1)
22
+
23
+ class_names = ["Normal", "Buried"]
24
+
25
+ # =========================
26
+ # 3. Preprocessing
27
+ # =========================
28
+ def prepare_image(image: Image.Image):
29
+ image = image.convert("RGB")
30
+ image = image.resize((224, 224))
31
+ img_array = np.array(image)
32
+ img_array = np.expand_dims(img_array, axis=0)
33
+ img_array = preprocess_input(img_array)
34
+ return img_array
35
+
36
+ # =========================
37
+ # 4. Prediction Logic
38
+ # =========================
39
+ def predict_image(image):
40
+ if image is None:
41
+ return "No image uploaded", 0.0, 0.0
42
+
43
+ processed = prepare_image(image)
44
+ prediction = model.predict(processed)[0][0]
45
+
46
+ prob_buried = float(prediction * 100)
47
+ prob_normal = float((1 - prediction) * 100)
48
+
49
+ label = "Buried Penis" if prediction > 0.5 else "Normal"
50
+
51
+ return label, round(prob_normal, 2), round(prob_buried, 2)
52
+
53
+ # =========================
54
+ # 5. FastAPI Endpoint
55
+ # =========================
 
 
56
  @app.post("/predict")
57
+ async def api_predict(file: UploadFile = File(...)):
58
+ image_bytes = await file.read()
59
+ image = Image.open(io.BytesIO(image_bytes))
60
+ label, normal, buried = predict_image(image)
61
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  return {
63
+ "class": label,
64
+ "probabilities": {
65
+ "normal": normal,
66
+ "buried": buried
67
+ }
68
  }
69
 
70
+ # =========================
71
+ # 6. Gradio UI
72
+ # =========================
73
+ with gr.Blocks() as demo:
74
+ gr.Markdown("""
75
+ # Ashoka Hipospadia Classifier API - DenseNet
76
+ **Medical screening tool for Buried Penis**
77
+
78
+ ⚠️ This tool is **NOT a diagnostic device**.
79
+ Results must be interpreted by **qualified medical professionals**.
80
+ """)
81
+
82
+ with gr.Row():
83
+ image_input = gr.Image(
84
+ type="pil",
85
+ label="Upload / Drag & Drop Medical Image"
86
+ )
87
+
88
+ classify_btn = gr.Button("Analyze Image")
89
+
90
+ result_label = gr.Textbox(label="Prediction Result")
91
+ prob_normal = gr.Number(label="Normal Probability (%)")
92
+ prob_buried = gr.Number(label="Buried Probability (%)")
93
+
94
+ classify_btn.click(
95
+ fn=predict_image,
96
+ inputs=image_input,
97
+ outputs=[result_label, prob_normal, prob_buried]
98
+ )
99
+
100
+ # =========================
101
+ # 7. Mount Gradio to FastAPI
102
+ # =========================
103
+ app = gr.mount_gradio_app(app, demo, path="/")