DimasMP3 commited on
Commit
d5412a0
·
1 Parent(s): 59d02f9
Files changed (2) hide show
  1. app.py +7 -18
  2. inference.py +103 -116
app.py CHANGED
@@ -1,15 +1,17 @@
 
1
  import gradio as gr
2
  from inference import predict, predict_batch
3
 
4
- # Tema dan tata letak diatur di sini
5
  with gr.Blocks(theme=gr.themes.Soft(), css=None, analytics_enabled=False) as iface:
6
  gr.Markdown(
7
  """
8
- # 🤖 Deteksi Bentuk Wajah (Indonesia)
9
- Unggah foto wajah Anda untuk mengetahui bentuknya. Model ini dilatih menggunakan arsitektur EfficientNetB2.
10
- Untuk hasil terbaik, gunakan foto di mana wajah terlihat jelas menghadap ke depan.
 
11
  """
12
  )
 
13
  with gr.Row():
14
  with gr.Column(scale=1):
15
  image_input = gr.Image(type="pil", label="Unggah Gambar Wajah")
@@ -17,27 +19,14 @@ with gr.Blocks(theme=gr.themes.Soft(), css=None, analytics_enabled=False) as ifa
17
  with gr.Column(scale=1):
18
  label_output = gr.Label(num_top_classes=3, label="Hasil Prediksi")
19
 
20
- # Bagian untuk menampilkan contoh gambar (opsional)
21
- gr.Examples(
22
- examples=[
23
- ],
24
- inputs=image_input,
25
- outputs=label_output,
26
- fn=predict,
27
- cache_examples=True
28
- )
29
-
30
- # Hubungkan tombol "Prediksi" dengan fungsi predict yang sudah diimpor
31
  submit_btn.click(fn=predict, inputs=image_input, outputs=label_output)
32
 
33
- # Endpoint batch (opsional) untuk klien JSON; menjaga compat endpoint default tetap single
34
  with gr.Accordion("Batch (opsional)", open=False):
35
  files_in = gr.Files(file_count="multiple", file_types=["image"], label="Unggah beberapa gambar")
36
  json_out = gr.JSON(label="Best of batch (JSON)")
37
  batch_btn = gr.Button("Prediksi Batch")
38
  batch_btn.click(fn=predict_batch, inputs=files_in, outputs=json_out, api_name="predict_batch")
39
 
40
- # Luncurkan aplikasi jika file ini dijalankan secara langsung
41
  if __name__ == "__main__":
42
  iface.launch()
43
-
 
1
+ # app.py
2
  import gradio as gr
3
  from inference import predict, predict_batch
4
 
 
5
  with gr.Blocks(theme=gr.themes.Soft(), css=None, analytics_enabled=False) as iface:
6
  gr.Markdown(
7
  """
8
+ # Deteksi Bentuk Wajah
9
+
10
+ Model **EfficientNetB4** (5 kelas: Heart, Oblong, Oval, Round, Square) yang dimuat dari `models/model.keras`.
11
+ Gunakan foto wajah frontal untuk hasil terbaik.
12
  """
13
  )
14
+
15
  with gr.Row():
16
  with gr.Column(scale=1):
17
  image_input = gr.Image(type="pil", label="Unggah Gambar Wajah")
 
19
  with gr.Column(scale=1):
20
  label_output = gr.Label(num_top_classes=3, label="Hasil Prediksi")
21
 
22
+ gr.Examples(examples=[], inputs=image_input, outputs=label_output, fn=predict, cache_examples=True)
 
 
 
 
 
 
 
 
 
 
23
  submit_btn.click(fn=predict, inputs=image_input, outputs=label_output)
24
 
 
25
  with gr.Accordion("Batch (opsional)", open=False):
26
  files_in = gr.Files(file_count="multiple", file_types=["image"], label="Unggah beberapa gambar")
27
  json_out = gr.JSON(label="Best of batch (JSON)")
28
  batch_btn = gr.Button("Prediksi Batch")
29
  batch_btn.click(fn=predict_batch, inputs=files_in, outputs=json_out, api_name="predict_batch")
30
 
 
31
  if __name__ == "__main__":
32
  iface.launch()
 
inference.py CHANGED
@@ -1,145 +1,132 @@
 
 
 
1
  import numpy as np
2
  from PIL import Image
3
  import tensorflow as tf
4
- from typing import List, Dict, Tuple, Any
5
- import time
6
- import os
7
-
8
- # --- 1. Konfigurasi Model ---
9
- LABELS: List[str] = ["Heart", "Oblong", "Oval", "Round", "Square"]
10
- IMG_SIZE = 244
11
-
12
- # --- 2. Fungsi Preprocessing (Tidak Berubah) ---
13
- def preprocess_image(image: Image.Image) -> np.ndarray:
14
- if image.mode != "RGB":
15
- image = image.convert("RGB")
16
- image = image.resize((IMG_SIZE, IMG_SIZE))
17
- image_array = np.asarray(image)
18
- image_array = np.expand_dims(image_array, axis=0)
19
- processed_array = tf.keras.applications.efficientnet.preprocess_input(image_array)
20
- return processed_array
21
 
22
- # --- 3. Kelas untuk Mengelola Model (Logika Baru) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  class FaceShapeModel:
24
- def __init__(self, model_path: str = "models/model.keras") -> None:
25
- """
26
- Membangun arsitektur model secara manual dan memuat bobotnya.
27
- Ini adalah solusi untuk masalah inkompatibilitas internal model.
28
- """
29
- print("Memulai proses pemuatan model dengan arsitektur baru...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  try:
31
-
32
- inputs = tf.keras.layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))
33
- base_model = tf.keras.applications.EfficientNetB2(
34
- weights=None,
35
- include_top=False,
36
- pooling='avg',
37
- input_tensor=inputs
38
- )
39
- x = tf.keras.layers.Dropout(0.4)(base_model.output)
40
- outputs = tf.keras.layers.Dense(len(LABELS), activation='softmax')(x)
41
-
42
- rebuilt_model = tf.keras.Model(inputs=inputs, outputs=outputs)
43
- print("Arsitektur model baru berhasil dibuat.")
44
-
45
- # 2. Muat HANYA bobotnya ke dalam arsitektur yang sudah benar
46
- full_path = os.path.join(os.getcwd(), model_path)
47
- print(f"Mencoba memuat bobot dari: {full_path}")
48
- rebuilt_model.load_weights(full_path)
49
-
50
- self.model = rebuilt_model
51
- print("Model berhasil dibangun ulang dan bobot telah dimuat.")
52
-
53
- # Warm-up: satu inference dummy agar jalur model siap (mengurangi cold-start)
54
- try:
55
- _ = self.model(tf.zeros((1, IMG_SIZE, IMG_SIZE, 3)))
56
- print("Warm-up inference completed.")
57
- except Exception as werr:
58
- print(f"Warm-up failed (non-fatal): {werr}")
59
-
60
  except Exception as e:
61
- print(f"Error saat memuat model di inference.py: {e}")
62
- self.model = None
 
 
 
 
 
 
 
 
63
 
64
  def predict_image(self, image: Image.Image) -> Dict[str, float]:
65
- """Melakukan prediksi pada satu gambar."""
66
- if not self.model:
67
- return {"Error": "Model tidak dapat dimuat."}
68
  if image is None:
69
- return {"Error": "Gambar tidak tersedia."}
70
-
71
  t0 = time.perf_counter()
72
- processed_input = preprocess_image(image)
73
- preds = self.model.predict(processed_input, verbose=0)[0]
74
- confidences: Dict[str, float] = {label: float(score) for label, score in zip(LABELS, preds)}
75
- # Logging ringan
76
- dt = (time.perf_counter() - t0) * 1000
77
- print(f"predict_image: took {dt:.1f} ms")
78
- return confidences
79
 
80
- # Buat instance dari model kita
81
- model_instance = FaceShapeModel()
82
 
 
83
  def predict(image_pil: Image.Image) -> Dict[str, float]:
84
- """Fungsi wrapper yang akan dipanggil oleh Gradio."""
85
- if model_instance:
86
- return model_instance.predict_image(image_pil)
87
  return {"Error": "Instance model tidak tersedia."}
88
 
89
-
90
- # --- Batch prediction opsional ---
91
  def _dict_to_payload(d: Dict[str, float]) -> Dict[str, object]:
92
- # Konversi dict label->score menjadi payload {label, confidences:[{label,confidence}]}
93
- if not d or any(k == "Error" for k in d.keys()):
94
  return {"label": "", "confidences": []}
95
- items: List[Tuple[str, float]] = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
96
- top = items[0][0] if items else ""
97
- confs = [{"label": k, "confidence": float(v)} for k, v in items]
98
- return {"label": top, "confidences": confs}
99
-
100
 
101
  def _to_pil_list(files: List[Any]) -> List[Image.Image]:
102
- out: List[Image.Image] = []
103
- for f in files:
104
  try:
105
  if isinstance(f, Image.Image):
106
- out.append(f)
107
- continue
108
- # Gradio >=4: FileData with .path
109
- path = None
110
- if hasattr(f, "path") and isinstance(getattr(f, "path"), str):
111
- path = getattr(f, "path")
112
- elif isinstance(f, str):
113
- path = f
114
- elif hasattr(f, "name") and isinstance(getattr(f, "name"), str):
115
- path = getattr(f, "name")
116
  if path:
117
- img = Image.open(path).convert("RGB")
118
- out.append(img)
119
  except Exception:
120
  continue
121
  return out
122
 
123
-
124
  def predict_batch(files: List[Any]) -> Dict[str, object]:
125
- """Prediksi batch: menerima FileData/paths atau PIL.Image; pilih hasil terbaik."""
126
- if not model_instance:
127
  return {"error": "Instance model tidak tersedia."}
128
- if not files:
129
- return {"error": "Tidak ada gambar."}
130
-
131
- images = _to_pil_list(files)
132
- if not images:
133
- return {"error": "Tidak dapat membaca gambar."}
134
-
135
- best_payload: Dict[str, object] = {"label": "", "confidences": []}
136
- best_conf = -1.0
137
- for img in images:
138
- d = model_instance.predict_image(img)
139
- payload = _dict_to_payload(d)
140
- conf = float(payload.get("confidences", [{"confidence": 0.0}])[0]["confidence"]) if payload.get("confidences") else 0.0
141
- if conf > best_conf:
142
- best_conf = conf
143
- best_payload = payload
144
- return best_payload
145
-
 
1
+ # inference.py
2
+ import os, json, time
3
+ from typing import List, Dict, Tuple, Any
4
  import numpy as np
5
  from PIL import Image
6
  import tensorflow as tf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ # ---------- Label handling ----------
9
+ _DEFAULT_LABELS = ["Heart","Oblong","Oval","Round","Square"]
10
+
11
+ def _load_labels() -> List[str]:
12
+ p_ci = os.path.join("models", "class_indices.json")
13
+ p_i2c = os.path.join("models", "idx2class.json")
14
+ # Prioritas: class_indices.json
15
+ try:
16
+ with open(p_ci, "r") as f:
17
+ d = json.load(f) # {"Label": idx, ...}
18
+ items = sorted(d.items(), key=lambda kv: kv[1])
19
+ labels = [k for k,_ in items]
20
+ print(f"[LABEL] from class_indices.json -> {labels}")
21
+ return labels
22
+ except Exception:
23
+ pass
24
+ # Fallback: idx2class.json
25
+ try:
26
+ with open(p_i2c, "r") as f:
27
+ d = json.load(f) # {"0":"Label",...}
28
+ items = sorted(((int(k),v) for k,v in d.items()), key=lambda kv: kv[0])
29
+ labels = [v for _,v in items]
30
+ print(f"[LABEL] from idx2class.json -> {labels}")
31
+ return labels
32
+ except Exception:
33
+ print(f"[LABEL] fallback default -> {_DEFAULT_LABELS}")
34
+ return _DEFAULT_LABELS
35
+
36
+ LABELS: List[str] = _load_labels()
37
+
38
+ # ---------- Model loader ----------
39
  class FaceShapeModel:
40
+ def __init__(self, model_path: str = os.path.join("models","model.keras")) -> None:
41
+ self.model = None
42
+ self.img_size = 224
43
+ self.external_rescale = False
44
+
45
+ full_path = os.path.join(os.getcwd(), model_path)
46
+ print(f"[LOAD] {full_path}")
47
+ self.model = tf.keras.models.load_model(full_path, compile=False)
48
+
49
+ # Ambil ukuran input dari model (None, H, W, 3)
50
+ ishape = self.model.input_shape
51
+ if isinstance(ishape, (list, tuple)):
52
+ h = ishape[1] if len(ishape) > 1 else None
53
+ if isinstance(h, int) and h > 0:
54
+ self.img_size = h
55
+ print(f"[MODEL] input img_size = {self.img_size}")
56
+
57
+ # Deteksi preprocessing internal (rescaling/normalization)
58
+ first_names = [l.name.lower() for l in self.model.layers[:8]]
59
+ has_internal_pp = any(("rescaling" in n) or ("normalization" in n) for n in first_names)
60
+ self.external_rescale = not has_internal_pp
61
+ print(f"[MODEL] internal_preproc={has_internal_pp} -> external_rescale={self.external_rescale}")
62
+
63
+ # Warm-up (optional)
64
  try:
65
+ _ = self.model(tf.zeros((1, self.img_size, self.img_size, 3), dtype=tf.float32))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  except Exception as e:
67
+ print("[WARN] warmup failed:", e)
68
+
69
+ def _preprocess(self, image: Image.Image) -> np.ndarray:
70
+ if image.mode != "RGB":
71
+ image = image.convert("RGB")
72
+ image = image.resize((self.img_size, self.img_size))
73
+ x = np.asarray(image, dtype=np.float32) # [0..255]
74
+ if self.external_rescale:
75
+ x = x / 255.0
76
+ return np.expand_dims(x, axis=0)
77
 
78
  def predict_image(self, image: Image.Image) -> Dict[str, float]:
79
+ if self.model is None:
80
+ return {"Error": "Model tidak dimuat."}
 
81
  if image is None:
82
+ return {"Error": "Gambar kosong."}
 
83
  t0 = time.perf_counter()
84
+ xin = self._preprocess(image)
85
+ probs = self.model.predict(xin, verbose=0)[0] # (C,)
86
+ out = {lbl: float(p) for lbl, p in zip(LABELS, probs)}
87
+ print(f"[INF] {len(LABELS)}-class in {(time.perf_counter()-t0)*1000:.1f} ms")
88
+ return out
 
 
89
 
90
+ # singleton
91
+ _model = FaceShapeModel()
92
 
93
+ # gradio APIs
94
  def predict(image_pil: Image.Image) -> Dict[str, float]:
95
+ if _model and _model.model is not None:
96
+ return _model.predict_image(image_pil)
 
97
  return {"Error": "Instance model tidak tersedia."}
98
 
99
+ # (opsional) batch
 
100
  def _dict_to_payload(d: Dict[str, float]) -> Dict[str, object]:
101
+ if not d or any(k == "Error" for k in d):
 
102
  return {"label": "", "confidences": []}
103
+ items = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
104
+ return {"label": items[0][0], "confidences": [{"label": k, "confidence": float(v)} for k,v in items]}
 
 
 
105
 
106
  def _to_pil_list(files: List[Any]) -> List[Image.Image]:
107
+ out = []
108
+ for f in files or []:
109
  try:
110
  if isinstance(f, Image.Image):
111
+ out.append(f); continue
112
+ path = getattr(f, "path", None) or getattr(f, "name", None) or (f if isinstance(f,str) else None)
 
 
 
 
 
 
 
 
113
  if path:
114
+ out.append(Image.open(path).convert("RGB"))
 
115
  except Exception:
116
  continue
117
  return out
118
 
 
119
  def predict_batch(files: List[Any]) -> Dict[str, object]:
120
+ if not _model or _model.model is None:
 
121
  return {"error": "Instance model tidak tersedia."}
122
+ imgs = _to_pil_list(files)
123
+ if not imgs:
124
+ return {"error": "Tidak ada gambar valid."}
125
+ best, bestc = {"label":"", "confidences":[]}, -1.0
126
+ for img in imgs:
127
+ d = _model.predict_image(img)
128
+ p = _dict_to_payload(d)
129
+ c = float(p["confidences"][0]["confidence"]) if p["confidences"] else 0.0
130
+ if c > bestc:
131
+ best, bestc = p, c
132
+ return best