Xaviant commited on
Commit
762fc67
·
1 Parent(s): eebe6ed

Creating an Endpoint

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -1
  2. app.py +74 -139
  3. requirements.txt +4 -2
Dockerfile CHANGED
@@ -13,4 +13,4 @@ COPY . .
13
  # Buka Port 7860 (Standar Hugging Face Spaces)
14
  EXPOSE 7860
15
 
16
- CMD ["streamlit", "run", "app.py", "--server.port", "7860", "--server.address", "0.0.0.0"]
 
13
  # Buka Port 7860 (Standar Hugging Face Spaces)
14
  EXPOSE 7860
15
 
16
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -1,62 +1,47 @@
1
- # ========================================
2
- # IMPORT LIBRARY
3
- # ========================================
4
- import streamlit as st
5
  import tensorflow as tf
6
  import numpy as np
7
  from PIL import Image
8
- from tensorflow.keras.applications.resnet50 import preprocess_input
9
  import sys
 
10
 
11
- # ========================================
12
- # 1. KONFIGURASI HALAMAN
13
- # ========================================
14
- st.set_page_config(
15
- page_title="Ashoka Hipospadia Classifier",
16
- page_icon="✨",
17
- layout="centered"
18
- )
19
 
20
- # ========================================
21
- # 2. LOAD MODEL
22
- # ========================================
23
- @st.cache_resource
24
- def load_model():
25
- """Load trained model dari file .h5"""
26
- try:
27
- print("Sedang memuat model...")
28
- model = tf.keras.models.load_model('cnn_kfold_best_model.h5')
29
- print("Model berhasil dimuat!")
30
- return model
31
- except Exception as e:
32
- st.error(f"Error memuat model: {e}")
33
- print(f"Error memuat model: {e}")
34
- return None
35
-
36
- model = load_model()
37
 
38
  # Label kelas: 0 = normal, 1 = buried
39
  class_names = ['normal', 'buried']
40
 
41
- # ========================================
42
- # 3. FUNGSI PREPROCESSING GAMBAR
43
- # ========================================
44
- def prepare_image(image):
45
  """
46
- Preprocessing gambar sebelum prediksi
47
  - Konversi ke RGB (3 channel)
48
  - Resize ke 224x224
49
  - Preprocessing ResNet50
50
  """
51
  try:
 
 
52
  # Paksa ubah ke RGB agar PNG transparan tidak error
53
- img = image.convert("RGB")
54
 
55
- # Resize ke ukuran input model
56
  img = img.resize((224, 224))
57
 
58
- # Convert ke numpy array dan tambah batch dimension
59
  img_array = np.array(img)
 
 
60
  img_array = np.expand_dims(img_array, axis=0)
61
 
62
  # Preprocessing ResNet50 (HARUS sama dengan training!)
@@ -67,111 +52,61 @@ def prepare_image(image):
67
  print(f"Error saat memproses gambar: {e}")
68
  return None
69
 
70
- # ========================================
71
- # 4. INTERFACE WEB
72
- # ========================================
73
-
74
- # Header
75
- st.title("✨ Ashoka Hipospadia Classifier")
76
- st.write("Upload an image for AI-powered classification")
77
- st.divider()
78
-
79
- # File uploader
80
- uploaded_file = st.file_uploader(
81
- "📁 Choose an image",
82
- type=['jpg', 'jpeg', 'png', 'bmp']
83
- )
84
-
85
- # ========================================
86
- # 5. LOGIKA PREDIKSI
87
- # ========================================
88
- if uploaded_file:
89
  try:
90
- # Baca gambar
91
- image = Image.open(uploaded_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- # Tampilkan preview
94
- st.image(image, caption="Uploaded Image", use_column_width=True)
 
 
 
 
 
 
 
 
95
 
96
- # Tombol prediksi
97
- if st.button("🔍 Analyze Image", type="primary", use_container_width=True):
98
- with st.spinner("🔄 Processing image..."):
99
-
100
- # Preprocessing
101
- processed_image = prepare_image(image)
102
-
103
- if processed_image is None:
104
- st.error("❌ File bukan gambar yang valid")
105
- elif model is None:
106
- st.error("❌ Model gagal dimuat")
107
- else:
108
- # Prediksi
109
- prediction = model.predict(processed_image, verbose=0)
110
- pred_value = float(prediction[0][0])
111
-
112
- # Hitung probabilitas
113
- prob_normal = (1 - pred_value) * 100
114
- prob_buried = pred_value * 100
115
-
116
- # Tentukan kelas (threshold 0.5)
117
- top_class_idx = 1 if pred_value > 0.5 else 0
118
- predicted_class = class_names[top_class_idx]
119
- confidence = max(prob_normal, prob_buried)
120
-
121
- # Hasil prediksi
122
- result = {
123
- "class": predicted_class,
124
- "confidence": confidence,
125
- "probabilities": {
126
- "normal": prob_normal,
127
- "buried": prob_buried
128
- }
129
- }
130
-
131
- # Tampilkan hasil
132
- st.divider()
133
- st.subheader("📊 Classification Results")
134
-
135
- # Status box
136
- if predicted_class == "normal":
137
- st.success(f"✅ Classification: **{predicted_class.upper()}**")
138
- else:
139
- st.error(f"⚠️ Classification: **{predicted_class.upper()}**")
140
-
141
- # Confidence metric
142
- st.metric(
143
- label="Confidence Level",
144
- value=f"{confidence:.1f}%",
145
- delta="High" if confidence > 80 else "Moderate"
146
- )
147
-
148
- # Detail probabilitas
149
- st.subheader("📈 Detailed Probability")
150
- col1, col2 = st.columns(2)
151
-
152
- with col1:
153
- st.metric("🟢 Normal", f"{prob_normal:.1f}%")
154
-
155
- with col2:
156
- st.metric("🔴 Buried", f"{prob_buried:.1f}%")
157
-
158
- # Visual progress bars
159
- st.write("**Visual Distribution:**")
160
- st.progress(prob_normal / 100, text=f"Normal: {prob_normal:.1f}%")
161
- st.progress(prob_buried / 100, text=f"Buried: {prob_buried:.1f}%")
162
-
163
  except Exception as e:
164
- st.error(f"❌ CRITICAL ERROR: {e}")
165
  print(f"CRITICAL ERROR: {e}")
 
166
 
167
- else:
168
- # ========================================
169
- # EMPTY STATE (belum upload gambar)
170
- # ========================================
171
- st.info("👆 Please upload an image to begin analysis")
 
 
 
 
172
 
173
- # ========================================
174
- # FOOTER
175
- # ========================================
176
- st.divider()
177
- st.caption("🚀 Powered by **Ashoka AI** | Deep Learning for Hipospadia Diagnosis")
 
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.resnet50 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.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 ResNet50
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!)
 
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": "ResNet50 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
 
 
 
requirements.txt CHANGED
@@ -1,4 +1,6 @@
1
  tensorflow-cpu>=2.16.0
2
- streamlit
 
 
3
  pillow
4
- numpy
 
1
  tensorflow-cpu>=2.16.0
2
+ fastapi
3
+ uvicorn
4
+ python-multipart
5
  pillow
6
+ numpy