zferd commited on
Commit
ec26494
·
verified ·
1 Parent(s): 15d423b

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +43 -4
streamlit_app.py CHANGED
@@ -4,6 +4,7 @@ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
4
  # Disable oneDNN custom ops to avoid the startup info line
5
  os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
6
 
 
7
  import json
8
  import numpy as np
9
  import pandas as pd
@@ -13,26 +14,32 @@ import tensorflow as tf
13
  tf.get_logger().setLevel("ERROR")
14
  from huggingface_hub import hf_hub_download
15
 
 
16
  from tensorflow.keras.applications.resnet50 import preprocess_input
17
 
 
18
  # ---------------- Streamlit page config ----------------
19
  st.set_page_config(page_title="Weld Defect Classifier", layout="centered")
20
 
 
21
  # ---- Mixed precision off on CPU to be safe
22
  tf.keras.mixed_precision.set_global_policy("float32")
23
 
24
 
 
25
  if 'upload' not in st.session_state:
26
  st.session_state.upload = None
27
  if 'probs' not in st.session_state:
28
  st.session_state.probs = None
29
 
 
30
  # ---- Hugging Face Repo Details --- #
31
  REPO_ID = "zferd/welding-defect"
32
  MODEL_FILENAME = "model/final_single_phase.keras"
33
  CONFIG_FILENAME = "model/training_config.json"
34
  IMG_SIZE = (224, 224)
35
 
 
36
  # ---- Pretty display labels
37
  DISPLAY_LABELS = {
38
  "PO": "PO (Porosity)",
@@ -43,15 +50,28 @@ DISPLAY_LABELS = {
43
  def pretty_label(code: str) -> str:
44
  return DISPLAY_LABELS.get(code, code)
45
 
 
46
  # ---- Confidence threshold for displaying the prediction
47
  THRESHOLD = 0.65
48
 
 
49
  @st.cache_resource
50
  def load_model_and_config_from_hub():
51
  """Downloads files from the Hub and loads model and config."""
52
- # Download model and config files
53
- model_path = hf_hub_download(repo_id=REPO_ID, filename=MODEL_FILENAME)
54
- config_path = hf_hub_download(repo_id=REPO_ID, filename=CONFIG_FILENAME)
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  # Load the Keras model
57
  model = tf.keras.models.load_model(model_path, compile=False)
@@ -63,6 +83,7 @@ def load_model_and_config_from_hub():
63
 
64
  return model, class_names
65
 
 
66
  def prepare_image(pil_img: Image.Image, target_size=(224, 224)) -> np.ndarray:
67
  """
68
  Letterbox (resize-with-pad) to target_size, fix EXIF orientation,
@@ -71,9 +92,11 @@ def prepare_image(pil_img: Image.Image, target_size=(224, 224)) -> np.ndarray:
71
  # 1) Honor camera EXIF orientation
72
  img = ImageOps.exif_transpose(pil_img)
73
 
 
74
  # 2) Convert to RGB (handles grayscale seamlessly)
75
  img = img.convert("RGB")
76
 
 
77
  # 3) Resize with aspect ratio preserved + pad to target (letterbox)
78
  img = ImageOps.pad(
79
  img,
@@ -82,27 +105,34 @@ def prepare_image(pil_img: Image.Image, target_size=(224, 224)) -> np.ndarray:
82
  color=(0, 0, 0),
83
  )
84
 
 
85
  # 4) To array, add batch dimension, preprocess like training
86
  x = np.asarray(img, dtype=np.float32)
87
  x = np.expand_dims(x, axis=0)
88
  x = preprocess_input(x)
89
  return x
90
 
 
91
  def upload_cb():
92
  st.session_state.upload = st.session_state.upload_k
93
  st.session_state.probs = None # reset because the user has new input
94
 
 
95
  def weld():
96
  st.title("🔎 Weld Defect Classifier")
97
 
 
98
  # Load resources from the Hub
 
99
  model, class_names = None, None
100
  try:
101
  model, class_names = load_model_and_config_from_hub()
102
  except Exception as e:
103
- st.error(f"Failed to load model: {e}")
 
104
  st.stop()
105
 
 
106
  st.file_uploader(
107
  "Upload an image", type=["jpg", "jpeg", "png", "bmp", "webp"],
108
  accept_multiple_files=False,
@@ -113,26 +143,32 @@ def weld():
113
  pil_img = Image.open(st.session_state.upload)
114
  st.image(pil_img, caption="Input image")
115
 
 
116
  image_batch = prepare_image(pil_img, IMG_SIZE)
117
 
 
118
  if st.session_state.probs is None:
119
  with st.spinner("Running inference..."):
120
  probs = model.predict(image_batch, verbose=0)[0].astype(float)
121
  st.session_state.probs = probs
122
 
 
123
  # Build DataFrame and add pretty labels
124
  df = pd.DataFrame({"class": class_names, "probability": st.session_state.probs})
125
  df["label"] = df["class"].map(pretty_label)
126
  df = df.sort_values("probability", ascending=False).reset_index(drop=True)
127
 
 
128
  # Top-1 with thresholding
129
  top_prob = float(df.loc[0, "probability"])
130
  top_label = df.loc[0, "label"]
131
  display_label = "Unclear" if top_prob < THRESHOLD else top_label
132
 
 
133
  st.subheader("Prediction")
134
  st.markdown(f"**{display_label}** — Confidence: {top_prob:.3f}")
135
 
 
136
  # All probabilities
137
  st.subheader("All class probabilities")
138
  st.dataframe(
@@ -140,6 +176,7 @@ def weld():
140
  .style.format({"probability": "{:.3f}"})
141
  )
142
 
 
143
  def credits():
144
  st.title('Credits')
145
  st.markdown('''
@@ -150,9 +187,11 @@ def credits():
150
  [3] [Github RIAWELC](https://github.com/stefyste/RIAWELC)
151
  ''')
152
 
 
153
  # --- Main app navigation ---
154
  weld_page = st.Page(weld, title="Weld Defect Classifier", default=True)
155
  credit_page = st.Page(credits, title="Credits")
156
 
 
157
  pg = st.navigation([weld_page, credit_page])
158
  pg.run()
 
4
  # Disable oneDNN custom ops to avoid the startup info line
5
  os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
6
 
7
+
8
  import json
9
  import numpy as np
10
  import pandas as pd
 
14
  tf.get_logger().setLevel("ERROR")
15
  from huggingface_hub import hf_hub_download
16
 
17
+
18
  from tensorflow.keras.applications.resnet50 import preprocess_input
19
 
20
+
21
  # ---------------- Streamlit page config ----------------
22
  st.set_page_config(page_title="Weld Defect Classifier", layout="centered")
23
 
24
+
25
  # ---- Mixed precision off on CPU to be safe
26
  tf.keras.mixed_precision.set_global_policy("float32")
27
 
28
 
29
+
30
  if 'upload' not in st.session_state:
31
  st.session_state.upload = None
32
  if 'probs' not in st.session_state:
33
  st.session_state.probs = None
34
 
35
+
36
  # ---- Hugging Face Repo Details --- #
37
  REPO_ID = "zferd/welding-defect"
38
  MODEL_FILENAME = "model/final_single_phase.keras"
39
  CONFIG_FILENAME = "model/training_config.json"
40
  IMG_SIZE = (224, 224)
41
 
42
+
43
  # ---- Pretty display labels
44
  DISPLAY_LABELS = {
45
  "PO": "PO (Porosity)",
 
50
  def pretty_label(code: str) -> str:
51
  return DISPLAY_LABELS.get(code, code)
52
 
53
+
54
  # ---- Confidence threshold for displaying the prediction
55
  THRESHOLD = 0.65
56
 
57
+
58
  @st.cache_resource
59
  def load_model_and_config_from_hub():
60
  """Downloads files from the Hub and loads model and config."""
61
+ # Get token from environment
62
+ token = os.environ.get("HF_TOKEN")
63
+
64
+ # Download model and config files with token
65
+ model_path = hf_hub_download(
66
+ repo_id=REPO_ID,
67
+ filename=MODEL_FILENAME,
68
+ token=token
69
+ )
70
+ config_path = hf_hub_download(
71
+ repo_id=REPO_ID,
72
+ filename=CONFIG_FILENAME,
73
+ token=token
74
+ )
75
 
76
  # Load the Keras model
77
  model = tf.keras.models.load_model(model_path, compile=False)
 
83
 
84
  return model, class_names
85
 
86
+
87
  def prepare_image(pil_img: Image.Image, target_size=(224, 224)) -> np.ndarray:
88
  """
89
  Letterbox (resize-with-pad) to target_size, fix EXIF orientation,
 
92
  # 1) Honor camera EXIF orientation
93
  img = ImageOps.exif_transpose(pil_img)
94
 
95
+
96
  # 2) Convert to RGB (handles grayscale seamlessly)
97
  img = img.convert("RGB")
98
 
99
+
100
  # 3) Resize with aspect ratio preserved + pad to target (letterbox)
101
  img = ImageOps.pad(
102
  img,
 
105
  color=(0, 0, 0),
106
  )
107
 
108
+
109
  # 4) To array, add batch dimension, preprocess like training
110
  x = np.asarray(img, dtype=np.float32)
111
  x = np.expand_dims(x, axis=0)
112
  x = preprocess_input(x)
113
  return x
114
 
115
+
116
  def upload_cb():
117
  st.session_state.upload = st.session_state.upload_k
118
  st.session_state.probs = None # reset because the user has new input
119
 
120
+
121
  def weld():
122
  st.title("🔎 Weld Defect Classifier")
123
 
124
+
125
  # Load resources from the Hub
126
+ error_box = st.empty()
127
  model, class_names = None, None
128
  try:
129
  model, class_names = load_model_and_config_from_hub()
130
  except Exception as e:
131
+ st.error(f"Error loading model from Hugging Face Hub: {str(e)}")
132
+ st.error("Make sure the model repository is accessible.")
133
  st.stop()
134
 
135
+
136
  st.file_uploader(
137
  "Upload an image", type=["jpg", "jpeg", "png", "bmp", "webp"],
138
  accept_multiple_files=False,
 
143
  pil_img = Image.open(st.session_state.upload)
144
  st.image(pil_img, caption="Input image")
145
 
146
+
147
  image_batch = prepare_image(pil_img, IMG_SIZE)
148
 
149
+
150
  if st.session_state.probs is None:
151
  with st.spinner("Running inference..."):
152
  probs = model.predict(image_batch, verbose=0)[0].astype(float)
153
  st.session_state.probs = probs
154
 
155
+
156
  # Build DataFrame and add pretty labels
157
  df = pd.DataFrame({"class": class_names, "probability": st.session_state.probs})
158
  df["label"] = df["class"].map(pretty_label)
159
  df = df.sort_values("probability", ascending=False).reset_index(drop=True)
160
 
161
+
162
  # Top-1 with thresholding
163
  top_prob = float(df.loc[0, "probability"])
164
  top_label = df.loc[0, "label"]
165
  display_label = "Unclear" if top_prob < THRESHOLD else top_label
166
 
167
+
168
  st.subheader("Prediction")
169
  st.markdown(f"**{display_label}** — Confidence: {top_prob:.3f}")
170
 
171
+
172
  # All probabilities
173
  st.subheader("All class probabilities")
174
  st.dataframe(
 
176
  .style.format({"probability": "{:.3f}"})
177
  )
178
 
179
+
180
  def credits():
181
  st.title('Credits')
182
  st.markdown('''
 
187
  [3] [Github RIAWELC](https://github.com/stefyste/RIAWELC)
188
  ''')
189
 
190
+
191
  # --- Main app navigation ---
192
  weld_page = st.Page(weld, title="Weld Defect Classifier", default=True)
193
  credit_page = st.Page(credits, title="Credits")
194
 
195
+
196
  pg = st.navigation([weld_page, credit_page])
197
  pg.run()