EnYa32 commited on
Commit
789b3f0
Β·
verified Β·
1 Parent(s): edc9ff6

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +110 -121
src/streamlit_app.py CHANGED
@@ -1,146 +1,135 @@
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
  import tensorflow as tf
8
- import matplotlib.pyplot as plt
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 CNN predicts 30 facial keypoint coordinates (x/y).')
22
 
 
23
 
24
- # -------------------------
25
- # Paths (HF-friendly: repo root)
26
- # -------------------------
27
  BASE_DIR = Path(__file__).resolve().parent
28
 
29
- MODEL_PATH = BASE_DIR / 'final_keypoints_cnn.keras'
 
 
 
30
  TARGET_COLS_PATH = BASE_DIR / 'target_cols.json'
31
- PREPROCESS_PATH = BASE_DIR / 'preprocess_config.json'
32
 
33
 
34
- # -------------------------
35
- # Loaders
36
- # -------------------------
37
  @st.cache_resource
38
- def load_model():
39
- if not MODEL_PATH.exists():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  raise FileNotFoundError(
41
- f'Model not found: {MODEL_PATH.name}. Put it in the repo root (same folder as app.py).'
 
 
42
  )
43
- return tf.keras.models.load_model(MODEL_PATH, compile=False)
44
-
45
-
46
- @st.cache_data
47
- def load_json(path: Path):
48
- if not path.exists():
49
- raise FileNotFoundError(
50
- f'File not found: {path.name}. Put it in the repo root (same folder as app.py).'
51
- )
52
- with open(path, 'r') as f:
53
- return json.load(f)
54
-
55
-
56
- model = load_model()
57
- target_cols = load_json(TARGET_COLS_PATH)
58
- pre_cfg = load_json(PREPROCESS_PATH)
59
 
60
- IMG_H, IMG_W = pre_cfg.get('img_size', [96, 96])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
-
63
- # -------------------------
64
- # Preprocess + Postprocess
65
- # -------------------------
66
- def preprocess_image(pil_img: Image.Image) -> np.ndarray:
67
- # Convert to grayscale like training data
68
- img = pil_img.convert('L')
69
- img = img.resize((IMG_W, IMG_H))
70
- arr = np.array(img, dtype=np.float32) # 0..255
71
- arr = arr / 255.0
72
- arr = arr.reshape(1, IMG_H, IMG_W, 1)
73
- return arr
74
-
75
-
76
- def denormalize_keypoints(pred_norm: np.ndarray) -> np.ndarray:
77
- # Training normalization: (y - 48) / 48 -> invert: y = pred*48 + 48
78
- pred = pred_norm * 48.0 + 48.0
79
- return pred
80
-
81
-
82
- def plot_keypoints(pil_img: Image.Image, keypoints_xy: np.ndarray):
83
- img = pil_img.convert('L').resize((IMG_W, IMG_H))
84
- xs = keypoints_xy[0::2]
85
- ys = keypoints_xy[1::2]
86
-
87
- fig, ax = plt.subplots(figsize=(5, 5))
88
- ax.imshow(img, cmap='gray')
89
- ax.scatter(xs, ys, s=25)
90
- ax.axis('off')
91
- return fig
92
-
93
-
94
- # -------------------------
95
- # UI
96
- # -------------------------
97
  uploaded = st.file_uploader('Upload an image (jpg/png)', type=['jpg', 'jpeg', 'png'])
98
 
99
- with st.expander('Settings', expanded=False):
100
- show_table = st.checkbox('Show predicted coordinates table', value=True)
101
- show_overlay = st.checkbox('Show keypoints overlay', value=True)
102
-
103
- if uploaded is None:
104
- st.info('Upload an image to get predictions.')
105
- st.stop()
106
-
107
- pil_img = Image.open(uploaded)
108
-
109
- st.subheader('Input Image')
110
- st.image(pil_img, use_container_width=True)
111
-
112
- x = preprocess_image(pil_img)
113
-
114
- with st.spinner('Predicting keypoints...'):
115
- pred_norm = model.predict(x, verbose=0)[0] # shape (30,)
116
- pred_px = denormalize_keypoints(pred_norm)
117
 
118
- # Build table
119
- rows = []
120
- for i, name in enumerate(target_cols):
121
- rows.append({'feature': name, 'value_px': float(pred_px[i])})
122
 
123
- if show_table:
124
- st.subheader('Predicted Keypoints (Pixels)')
125
- st.dataframe(rows, use_container_width=True)
126
 
127
- if show_overlay:
128
- st.subheader('Overlay')
129
- fig = plot_keypoints(pil_img, pred_px)
130
- st.pyplot(fig, clear_figure=True)
131
 
132
- # Optional: downloadable single-image "submission-like" csv (ImageId=1)
133
- st.subheader('Download (optional)')
134
- st.caption('This creates a single-image submission-like file: ImageId=1, RowId=1..30.')
135
 
136
- csv_lines = ['RowId,ImageId,FeatureName,Location']
137
- for idx, (name, val) in enumerate(zip(target_cols, pred_px), start=1):
138
- csv_lines.append(f'{idx},1,{name},{val}')
139
- csv_text = '\n'.join(csv_lines)
140
 
141
- st.download_button(
142
- label='Download predicted_keypoints.csv',
143
- data=csv_text.encode('utf-8'),
144
- file_name='predicted_keypoints.csv',
145
- mime='text/csv'
146
- )
 
 
 
 
 
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.')