EnYa32 commited on
Commit
a3c0988
·
verified ·
1 Parent(s): e8af3c1

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +134 -93
src/streamlit_app.py CHANGED
@@ -1,135 +1,176 @@
1
  import json
 
 
2
  import numpy as np
3
  import streamlit as st
4
  from PIL import Image
 
5
  import tensorflow as tf
6
- from pathlib import Path
7
 
8
- st.set_page_config(page_title='Facial Keypoints Predictor (CNN)', page_icon='🙂', layout='centered')
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  BASE_DIR = Path(__file__).resolve().parent
11
 
12
- # --- Choose ONE of these model paths depending on what you uploaded ---
13
- MODEL_SAVEDMODEL_DIR = BASE_DIR / 'final_keypoints_cnn_savedmodel' # folder (recommended)
14
- MODEL_H5_PATH = BASE_DIR / 'final_keypoints_cnn.h5' # file (alternative)
15
 
16
  TARGET_COLS_PATH = BASE_DIR / 'target_cols.json'
17
- PREPROCESS_CFG_PATH = BASE_DIR / 'preprocess_config.json'
18
 
19
 
 
 
 
20
  @st.cache_resource
21
  def load_assets():
22
- # load metadata
 
 
 
 
 
 
 
 
 
 
 
 
23
  if not TARGET_COLS_PATH.exists():
24
- raise FileNotFoundError(f'Missing {TARGET_COLS_PATH.name} in repo root.')
 
 
 
25
 
26
  with open(TARGET_COLS_PATH, 'r') as f:
27
  target_cols = json.load(f)
28
 
29
- preprocess = {'img_size': [96, 96], 'normalize': 'x / 255.0', 'target_normalization': '(y - 48) / 48'}
30
- if PREPROCESS_CFG_PATH.exists():
31
- with open(PREPROCESS_CFG_PATH, 'r') as f:
32
- preprocess = json.load(f)
33
 
34
- # load model (prefer SavedModel folder)
35
- if MODEL_SAVEDMODEL_DIR.exists():
36
- model = tf.keras.models.load_model(str(MODEL_SAVEDMODEL_DIR), compile=False)
37
- elif MODEL_H5_PATH.exists():
38
- model = tf.keras.models.load_model(str(MODEL_H5_PATH), compile=False)
39
- else:
40
- raise FileNotFoundError(
41
- 'Model not found. Upload either:\n'
42
- f'- {MODEL_SAVEDMODEL_DIR.name}/ (SavedModel folder)\n'
43
- f'- {MODEL_H5_PATH.name} (H5 file)\n'
44
- )
45
 
46
- return model, target_cols, preprocess
47
-
48
-
49
- def preprocess_image(pil_img, img_size=(96, 96)):
50
- img = pil_img.convert('L').resize(img_size) # grayscale
51
- x = np.array(img, dtype=np.float32)
52
- x = x / 255.0
53
- x = np.expand_dims(x, axis=-1) # (H, W, 1)
54
- x = np.expand_dims(x, axis=0) # (1, H, W, 1)
55
- return x, np.array(img)
56
-
57
-
58
- def denormalize_and_clip(y_pred):
59
- # your training normalization: y_norm = (y - 48) / 48
60
- # so inverse: y = y_norm * 48 + 48
61
- y = y_pred * 48.0 + 48.0
62
- y = np.clip(y, 0.0, 96.0)
63
- return y
64
-
65
-
66
- def keypoints_to_xy(y_vec, target_cols):
67
- # y_vec: shape (30,)
68
- coords = {}
69
- for i, name in enumerate(target_cols):
70
- coords[name] = float(y_vec[i])
71
- return coords
72
-
73
-
74
- def draw_points_on_image(gray_img_96, coords, point_size=2):
75
- # gray_img_96: (96,96) uint8
76
- rgb = np.stack([gray_img_96]*3, axis=-1).copy()
77
- # draw red dots
78
- for k, v in coords.items():
79
- if k.endswith('_x'):
80
- y_name = k.replace('_x', '_y')
81
- if y_name in coords:
82
- x = int(round(coords[k]))
83
- y = int(round(coords[y_name]))
84
- if 0 <= x < 96 and 0 <= y < 96:
85
- x0, x1 = max(0, x-point_size), min(95, x+point_size)
86
- y0, y1 = max(0, y-point_size), min(95, y+point_size)
87
- rgb[y0:y1+1, x0:x1+1, 0] = 255
88
- rgb[y0:y1+1, x0:x1+1, 1] = 0
89
- rgb[y0:y1+1, x0:x1+1, 2] = 0
90
- return rgb
91
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- st.title('🙂 Facial Keypoints Predictor (CNN)')
94
- st.write('Upload a face image and the model will predict 15 facial keypoints (30 values: x/y).')
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  with st.expander('Model files checklist'):
97
  st.markdown(
98
- '- **target_cols.json** \n'
99
- '- **preprocess_config.json** ✅ (optional)\n'
100
- '- **Model**: `final_keypoints_cnn_savedmodel/` (recommended) OR `final_keypoints_cnn.h5`\n'
 
 
 
 
 
 
101
  )
102
 
103
- model, target_cols, preprocess_cfg = load_assets()
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  uploaded = st.file_uploader('Upload an image (jpg/png)', type=['jpg', 'jpeg', 'png'])
106
 
107
  if uploaded is not None:
108
  pil_img = Image.open(uploaded)
109
- st.image(pil_img, caption='Uploaded image', use_container_width=True)
 
 
 
110
 
111
- x, gray_96 = preprocess_image(pil_img, img_size=tuple(preprocess_cfg.get('img_size', [96, 96])))
 
112
 
113
- y_pred_norm = model.predict(x, verbose=0)[0] # (30,)
114
- y_pred = denormalize_and_clip(y_pred_norm) # (30,)
 
 
115
 
116
- coords = keypoints_to_xy(y_pred, target_cols)
 
117
 
118
- st.subheader('Predicted keypoints (first 10 values)')
119
- preview_items = list(coords.items())[:10]
120
- st.write({k: round(v, 3) for k, v in preview_items})
121
 
122
- overlay = draw_points_on_image(gray_96, coords, point_size=2)
123
- st.subheader('Keypoints overlay (96×96)')
124
  st.image(overlay, use_container_width=False)
125
 
126
- # Download as JSON
127
- out = {'img_size': preprocess_cfg.get('img_size', [96, 96]), 'predictions': coords}
128
- st.download_button(
129
- 'Download predictions as JSON',
130
- data=json.dumps(out, indent=2),
131
- file_name='predicted_keypoints.json',
132
- mime='application/json'
133
- )
 
 
 
 
 
 
 
 
 
134
  else:
135
- st.info('Upload an image to run inference.')
 
1
  import json
2
+ from pathlib import Path
3
+
4
  import numpy as np
5
  import streamlit as st
6
  from PIL import Image
7
+
8
  import tensorflow as tf
 
9
 
 
10
 
11
+ # -------------------------
12
+ # Page config
13
+ # -------------------------
14
+ st.set_page_config(
15
+ page_title='Facial Keypoints Predictor (CNN)',
16
+ page_icon='🙂',
17
+ layout='centered'
18
+ )
19
+
20
+ st.title('🙂 Facial Keypoints Predictor (CNN)')
21
+ st.write('Upload a face image and the model will predict 15 facial keypoints (30 values: x/y).')
22
+
23
+
24
+ # -------------------------
25
+ # Paths (HuggingFace friendly)
26
+ # Put ALL files inside /src
27
+ # -------------------------
28
  BASE_DIR = Path(__file__).resolve().parent
29
 
30
+ MODEL_KERAS_PATH = BASE_DIR / 'final_keypoints_cnn.keras'
31
+ MODEL_H5_PATH = BASE_DIR / 'final_keypoints_cnn.h5'
 
32
 
33
  TARGET_COLS_PATH = BASE_DIR / 'target_cols.json'
34
+ PREPROCESS_PATH = BASE_DIR / 'preprocess_config.json'
35
 
36
 
37
+ # -------------------------
38
+ # Load assets
39
+ # -------------------------
40
  @st.cache_resource
41
  def load_assets():
42
+ # ✅ IMPORTANT: Keras 3 does NOT load SavedModel folders via load_model()
43
+ # So we FORCE .keras or .h5 only.
44
+ if MODEL_KERAS_PATH.exists():
45
+ model = tf.keras.models.load_model(str(MODEL_KERAS_PATH), compile=False)
46
+ model_source = MODEL_KERAS_PATH.name
47
+ elif MODEL_H5_PATH.exists():
48
+ model = tf.keras.models.load_model(str(MODEL_H5_PATH), compile=False)
49
+ model_source = MODEL_H5_PATH.name
50
+ else:
51
+ raise FileNotFoundError(
52
+ 'Model not found. Upload `final_keypoints_cnn.keras` (recommended) or `final_keypoints_cnn.h5` into /src.'
53
+ )
54
+
55
  if not TARGET_COLS_PATH.exists():
56
+ raise FileNotFoundError('Missing file: target_cols.json (put it in /src)')
57
+
58
+ if not PREPROCESS_PATH.exists():
59
+ raise FileNotFoundError('Missing file: preprocess_config.json (put it in /src)')
60
 
61
  with open(TARGET_COLS_PATH, 'r') as f:
62
  target_cols = json.load(f)
63
 
64
+ with open(PREPROCESS_PATH, 'r') as f:
65
+ preprocess_cfg = json.load(f)
 
 
66
 
67
+ return model, target_cols, preprocess_cfg, model_source
 
 
 
 
 
 
 
 
 
 
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ # -------------------------
71
+ # Helpers
72
+ # -------------------------
73
+ def preprocess_image(pil_img: Image.Image, img_size=(96, 96)) -> np.ndarray:
74
+ # Convert to grayscale like the Kaggle dataset (96x96, 1 channel)
75
+ img = pil_img.convert('L').resize(img_size)
76
+ arr = np.array(img).astype(np.float32) / 255.0 # normalize x / 255
77
+ arr = np.expand_dims(arr, axis=-1) # (96, 96, 1)
78
+ arr = np.expand_dims(arr, axis=0) # (1, 96, 96, 1)
79
+ return arr
80
 
 
 
81
 
82
+ def draw_keypoints(pil_img: Image.Image, keypoints_xy: np.ndarray) -> Image.Image:
83
+ # keypoints_xy shape: (15, 2) -> x,y
84
+ import PIL.ImageDraw as ImageDraw
85
+
86
+ img = pil_img.convert('RGB').resize((96, 96))
87
+ draw = ImageDraw.Draw(img)
88
+
89
+ for (x, y) in keypoints_xy:
90
+ r = 2
91
+ draw.ellipse((x - r, y - r, x + r, y + r), outline='red', width=2)
92
+ return img
93
+
94
+
95
+ def to_xy(pred_30: np.ndarray) -> np.ndarray:
96
+ # pred_30 shape: (30,)
97
+ pts = pred_30.reshape(-1, 2)
98
+ return pts
99
+
100
+
101
+ # -------------------------
102
+ # UI: checklist
103
+ # -------------------------
104
  with st.expander('Model files checklist'):
105
  st.markdown(
106
+ '- Put files inside **`/src`** in your HuggingFace Space.\n'
107
+ '- Required:\n'
108
+ ' - `final_keypoints_cnn.keras` (recommended) **or** `final_keypoints_cnn.h5`\n'
109
+ ' - `target_cols.json`\n'
110
+ ' - `preprocess_config.json`\n'
111
+ '- Optional: `history.pkl` (not needed for inference)\n'
112
+ '\n'
113
+ '✅ Tip: If you still have a folder `final_keypoints_cnn_savedmodel/`, remove it or ignore it. '
114
+ 'This app does **not** load SavedModel folders.'
115
  )
116
 
 
117
 
118
+ # -------------------------
119
+ # Load model + configs
120
+ # -------------------------
121
+ try:
122
+ model, target_cols, preprocess_cfg, model_source = load_assets()
123
+ st.success(f'Model loaded: {model_source}')
124
+ except Exception as e:
125
+ st.error(str(e))
126
+ st.stop()
127
+
128
+
129
+ # -------------------------
130
+ # Upload + Predict
131
+ # -------------------------
132
  uploaded = st.file_uploader('Upload an image (jpg/png)', type=['jpg', 'jpeg', 'png'])
133
 
134
  if uploaded is not None:
135
  pil_img = Image.open(uploaded)
136
+ st.subheader('Input image')
137
+ st.image(pil_img, use_container_width=True)
138
+
139
+ x = preprocess_image(pil_img, img_size=(96, 96))
140
 
141
+ # Predict
142
+ pred = model.predict(x, verbose=0)[0] # shape (30,)
143
 
144
+ # If your model predicts normalized coordinates, you must de-normalize:
145
+ # Your training: (y - 48) / 48 => inference: y = y_pred * 48 + 48
146
+ # We do it safely here:
147
+ pred = (pred * 48.0) + 48.0
148
 
149
+ # Clip to valid [0, 96]
150
+ pred = np.clip(pred, 0.0, 96.0)
151
 
152
+ pts = to_xy(pred)
 
 
153
 
154
+ st.subheader('Prediction (keypoints on 96×96)')
155
+ overlay = draw_keypoints(pil_img, pts)
156
  st.image(overlay, use_container_width=False)
157
 
158
+ st.subheader('Keypoints table (x, y)')
159
+ # Build a nice table using target_cols order
160
+ # target_cols is typically like: ['left_eye_center_x', 'left_eye_center_y', ...]
161
+ rows = []
162
+ for i in range(0, len(target_cols), 2):
163
+ name_x = target_cols[i]
164
+ name_y = target_cols[i + 1]
165
+ rows.append({
166
+ 'keypoint': name_x.replace('_x', ''),
167
+ 'x_name': name_x,
168
+ 'y_name': name_y,
169
+ 'x': float(pred[i]),
170
+ 'y': float(pred[i + 1]),
171
+ })
172
+
173
+ st.dataframe(rows, use_container_width=True)
174
+
175
  else:
176
+ st.info('Upload an image to get predictions.')