zferd commited on
Commit
5951966
·
verified ·
1 Parent(s): 2d9bc18

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +74 -49
streamlit_app.py CHANGED
@@ -1,45 +1,39 @@
1
  import os
 
2
  # Quieter TensorFlow C++ logs: 0=all, 1=warn, 2=error, 3=fatal
3
  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
-
8
  import json
9
  import numpy as np
10
  import pandas as pd
11
  import streamlit as st
12
  from PIL import Image, ImageOps
13
  import tensorflow as tf
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.h5"
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)",
@@ -47,6 +41,8 @@ DISPLAY_LABELS = {
47
  "LP": "LP (Lack of Penetration)",
48
  "ND": "ND (No Defect)",
49
  }
 
 
50
  def pretty_label(code: str) -> str:
51
  return DISPLAY_LABELS.get(code, code)
52
 
@@ -55,32 +51,63 @@ def pretty_label(code: str) -> str:
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)
78
-
 
 
 
 
 
 
 
 
79
  # Load class names from the config file
80
  with open(config_path, "r") as f:
81
  cfg = json.load(f)
82
- class_names = cfg.get("class_names", ["CR", "LP", "ND", "PO"]) # Fallback
83
-
84
  return model, class_names
85
 
86
 
@@ -92,11 +119,9 @@ def prepare_image(pil_img: Image.Image, target_size=(224, 224)) -> np.ndarray:
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,7 +130,6 @@ def prepare_image(pil_img: Image.Image, target_size=(224, 224)) -> np.ndarray:
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)
@@ -121,77 +145,78 @@ def upload_cb():
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,
139
  on_change=upload_cb,
140
- key='upload_k')
141
-
 
142
  if st.session_state.upload and model is not None and class_names:
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(
175
- df[["label", "probability"]].rename(columns={"label": "Class"})
 
176
  .style.format({"probability": "{:.3f}"})
177
  )
178
 
179
 
180
  def credits():
181
- st.title('Credits')
182
- st.markdown('''
183
- [1] Benito Totino, Fanny Spagnolo, Stefania Perri, "RIAWELC: A Novel Dataset of Radiographic Images for Automatic Weld Defects Classification", in the Proceedings of the Interdisciplinary Conference on Mechanics, Computers and Electrics (ICMECE 2022), 6-7 October 2022, Barcelona, Spain.
184
-
185
- [2] Stefania Perri, Fanny Spagnolo, Fabio Frustaci, Pasquale Corsonello, "Welding Defects Classification Through a Convolutional Neural Network", in press in Manufacturing Letters, Elsevier.
186
-
 
 
 
 
 
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()
 
1
  import os
2
+
3
  # Quieter TensorFlow C++ logs: 0=all, 1=warn, 2=error, 3=fatal
4
  os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
5
  # Disable oneDNN custom ops to avoid the startup info line
6
  os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
7
 
 
8
  import json
9
  import numpy as np
10
  import pandas as pd
11
  import streamlit as st
12
  from PIL import Image, ImageOps
13
  import tensorflow as tf
 
14
  from huggingface_hub import hf_hub_download
 
 
15
  from tensorflow.keras.applications.resnet50 import preprocess_input
16
 
17
+ tf.get_logger().setLevel("ERROR")
18
 
19
  # ---------------- Streamlit page config ----------------
20
  st.set_page_config(page_title="Weld Defect Classifier", layout="centered")
21
 
 
22
  # ---- Mixed precision off on CPU to be safe
23
  tf.keras.mixed_precision.set_global_policy("float32")
24
 
25
+ # ---- Session state
26
+ if "upload" not in st.session_state:
 
27
  st.session_state.upload = None
28
+ if "probs" not in st.session_state:
29
  st.session_state.probs = None
30
 
 
31
  # ---- Hugging Face Repo Details --- #
32
  REPO_ID = "zferd/welding-defect"
33
  MODEL_FILENAME = "model/final_single_phase.h5"
34
  CONFIG_FILENAME = "model/training_config.json"
35
  IMG_SIZE = (224, 224)
36
 
 
37
  # ---- Pretty display labels
38
  DISPLAY_LABELS = {
39
  "PO": "PO (Porosity)",
 
41
  "LP": "LP (Lack of Penetration)",
42
  "ND": "ND (No Defect)",
43
  }
44
+
45
+
46
  def pretty_label(code: str) -> str:
47
  return DISPLAY_LABELS.get(code, code)
48
 
 
51
  THRESHOLD = 0.65
52
 
53
 
54
+ # ---------- Custom layer to handle unknown "Cast" ----------
55
+ class CastLayer(tf.keras.layers.Layer):
56
+ """
57
+ Minimal custom layer used to replace the unknown 'Cast' layer
58
+ when loading the saved model from H5.
59
+
60
+ If the original object was effectively just casting to float32,
61
+ this reproduces that behavior.
62
+ """
63
+
64
+ def __init__(self, dtype="float32", **kwargs):
65
+ super().__init__(**kwargs)
66
+ self.target_dtype = tf.dtypes.as_dtype(dtype)
67
+
68
+ def call(self, inputs):
69
+ return tf.cast(inputs, self.target_dtype)
70
+
71
+ def get_config(self):
72
+ config = super().get_config()
73
+ config.update({"dtype": self.target_dtype.name})
74
+ return config
75
+
76
+
77
  @st.cache_resource
78
  def load_model_and_config_from_hub():
79
  """Downloads files from the Hub and loads model and config."""
80
+ # Get token from environment (set in HF Space secrets if repo is private)
81
  token = os.environ.get("HF_TOKEN")
82
+
83
  # Download model and config files with token
84
  model_path = hf_hub_download(
85
+ repo_id=REPO_ID,
86
  filename=MODEL_FILENAME,
87
+ token=token,
88
  )
89
  config_path = hf_hub_download(
90
+ repo_id=REPO_ID,
91
  filename=CONFIG_FILENAME,
92
+ token=token,
93
  )
94
+
95
+ # Load the Keras model with custom_objects so that 'Cast' is known
96
+ custom_objects = {
97
+ "Cast": CastLayer,
98
+ }
99
+
100
+ model = tf.keras.models.load_model(
101
+ model_path,
102
+ compile=False,
103
+ custom_objects=custom_objects,
104
+ )
105
+
106
  # Load class names from the config file
107
  with open(config_path, "r") as f:
108
  cfg = json.load(f)
109
+ class_names = cfg.get("class_names", ["CR", "LP", "ND", "PO"]) # Fallback
110
+
111
  return model, class_names
112
 
113
 
 
119
  # 1) Honor camera EXIF orientation
120
  img = ImageOps.exif_transpose(pil_img)
121
 
 
122
  # 2) Convert to RGB (handles grayscale seamlessly)
123
  img = img.convert("RGB")
124
 
 
125
  # 3) Resize with aspect ratio preserved + pad to target (letterbox)
126
  img = ImageOps.pad(
127
  img,
 
130
  color=(0, 0, 0),
131
  )
132
 
 
133
  # 4) To array, add batch dimension, preprocess like training
134
  x = np.asarray(img, dtype=np.float32)
135
  x = np.expand_dims(x, axis=0)
 
145
  def weld():
146
  st.title("🔎 Weld Defect Classifier")
147
 
 
148
  # Load resources from the Hub
 
149
  model, class_names = None, None
150
  try:
151
  model, class_names = load_model_and_config_from_hub()
152
  except Exception as e:
153
  st.error(f"Error loading model from Hugging Face Hub: {str(e)}")
154
+ st.error("Make sure the model repository is accessible and HF_TOKEN is set if needed.")
155
  st.stop()
156
 
 
157
  st.file_uploader(
158
+ "Upload an image",
159
+ type=["jpg", "jpeg", "png", "bmp", "webp"],
160
  accept_multiple_files=False,
161
  on_change=upload_cb,
162
+ key="upload_k",
163
+ )
164
+
165
  if st.session_state.upload and model is not None and class_names:
166
  pil_img = Image.open(st.session_state.upload)
167
  st.image(pil_img, caption="Input image")
168
 
 
169
  image_batch = prepare_image(pil_img, IMG_SIZE)
170
 
 
171
  if st.session_state.probs is None:
172
  with st.spinner("Running inference..."):
173
  probs = model.predict(image_batch, verbose=0)[0].astype(float)
174
  st.session_state.probs = probs
175
 
 
176
  # Build DataFrame and add pretty labels
177
+ df = pd.DataFrame(
178
+ {"class": class_names, "probability": st.session_state.probs}
179
+ )
180
  df["label"] = df["class"].map(pretty_label)
181
  df = df.sort_values("probability", ascending=False).reset_index(drop=True)
182
 
 
183
  # Top-1 with thresholding
184
  top_prob = float(df.loc[0, "probability"])
185
  top_label = df.loc[0, "label"]
186
  display_label = "Unclear" if top_prob < THRESHOLD else top_label
187
 
 
188
  st.subheader("Prediction")
189
  st.markdown(f"**{display_label}** — Confidence: {top_prob:.3f}")
190
 
 
191
  # All probabilities
192
  st.subheader("All class probabilities")
193
  st.dataframe(
194
+ df[["label", "probability"]]
195
+ .rename(columns={"label": "Class"})
196
  .style.format({"probability": "{:.3f}"})
197
  )
198
 
199
 
200
  def credits():
201
+ st.title("Credits")
202
+ st.markdown(
203
+ """
204
+ [1] Benito Totino, Fanny Spagnolo, Stefania Perri,
205
+ "RIAWELC: A Novel Dataset of Radiographic Images for Automatic Weld Defects Classification",
206
+ ICMECE 2022, Barcelona, Spain.
207
+
208
+ [2] Stefania Perri, Fanny Spagnolo, Fabio Frustaci, Pasquale Corsonello,
209
+ "Welding Defects Classification Through a Convolutional Neural Network",
210
+ Manufacturing Letters, Elsevier.
211
+
212
  [3] [Github RIAWELC](https://github.com/stefyste/RIAWELC)
213
+ """
214
+ )
215
 
216
 
217
  # --- Main app navigation ---
218
  weld_page = st.Page(weld, title="Weld Defect Classifier", default=True)
219
  credit_page = st.Page(credits, title="Credits")
220
 
 
221
  pg = st.navigation([weld_page, credit_page])
222
  pg.run()