anamjafar6 commited on
Commit
b856d6d
Β·
verified Β·
1 Parent(s): fca69ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +131 -64
app.py CHANGED
@@ -1,5 +1,8 @@
 
 
 
 
1
 
2
- # ---- Import Libraries ----
3
  import streamlit as st
4
  import numpy as np
5
  import cv2
@@ -7,14 +10,10 @@ from PIL import Image
7
  from tensorflow.keras.models import load_model
8
  from streamlit_drawable_canvas import st_canvas
9
 
10
- # ---- Page Config ----
11
- st.set_page_config(
12
- page_title="Digit Recognition App",
13
- page_icon="πŸ”’",
14
- layout="wide"
15
- )
16
 
17
- # ---- Custom Background with CSS ----
18
  st.markdown(
19
  """
20
  <style>
@@ -26,34 +25,94 @@ st.markdown(
26
  unsafe_allow_html=True
27
  )
28
 
29
- # ---- Load Model ----
30
  @st.cache_resource
31
  def load_cnn_model():
32
  return load_model("mnist_cnn.h5")
33
 
34
  model = load_cnn_model()
35
 
36
- # ---- Header ----
37
- st.markdown(
38
- "<h1 style='text-align: center; color: #0D47A1;'> πŸ”’ Handwritten Digit Recognizer </h1>",
39
- unsafe_allow_html=True
40
- )
41
- st.write("Upload or draw a digit (0–9), and the model will predict it.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  st.markdown("---")
43
 
44
- # ---- Sidebar ----
45
  st.sidebar.header("πŸ“Œ Instructions")
46
  st.sidebar.info(
47
- "1. Upload a clear image of a single digit (PNG/JPG), **or draw your digit below**.\n"
48
- "2. The image will be preprocessed automatically.\n"
49
- "3. The model predicts the digit and confidence level."
50
  )
51
-
52
  st.sidebar.markdown("---")
53
  st.sidebar.write("πŸ‘©β€πŸ’» **About**: Built with ❀️ by **Anam Jafar**")
54
- st.sidebar.write("[πŸ”— Connect on LinkedIn](https://www.linkedin.com/in/anam-jafar)")
55
 
56
- # ---- File Upload ----
 
 
57
  uploaded_files = st.file_uploader(
58
  "πŸ“‚ Upload digit images (single or multiple):",
59
  type=["png", "jpg", "jpeg"],
@@ -62,21 +121,30 @@ uploaded_files = st.file_uploader(
62
 
63
  if uploaded_files:
64
  st.subheader("πŸ“· Uploaded Images & Predictions")
65
- cols = st.columns(len(uploaded_files)) # grid layout
66
-
67
- for idx, file in enumerate(uploaded_files):
68
- img = Image.open(file).convert('L').resize((28, 28))
69
- img_array = np.array(img) / 255.0
70
- img_array = img_array.reshape(1, 28, 28, 1)
71
-
72
- pred = model.predict(img_array)
73
- pred_label = np.argmax(pred)
74
- confidence = np.max(pred)
75
-
76
- with cols[idx]:
77
- st.image(file, caption=f"Pred: {pred_label} ({confidence*100:.1f}%)", width=120)
78
 
79
- # ---- Drawing Pad ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  st.subheader("πŸ–ŒοΈ Draw your digit here:")
81
  canvas_result = st_canvas(
82
  stroke_width=12,
@@ -88,32 +156,31 @@ canvas_result = st_canvas(
88
  key="canvas",
89
  )
90
 
91
- if canvas_result.image_data is not None:
92
- img = cv2.resize(canvas_result.image_data.astype("uint8"), (28, 28))
93
- img = cv2.cvtColor(img, cv2.COLOR_RGBA2GRAY)
94
- img = img / 255.0
95
- img = img.reshape(1, 28, 28, 1)
96
-
97
- pred = model.predict(img)
98
- pred_label = np.argmax(pred)
99
- confidence = np.max(pred)
100
-
101
- st.markdown(
102
- f"""
103
- <div style="padding:15px; border-radius:10px; background-color:#FFF3CD; text-align:center;">
104
- <h2 style="color:#D32F2F;"> 🎯 Predicted Digit: {pred_label} </h2>
105
- <p>Confidence: {confidence*100:.2f}%</p>
106
- </div>
107
- """,
108
- unsafe_allow_html=True
109
- )
110
-
111
- # Probability bar chart
112
- st.bar_chart(pred[0])
113
-
114
- # ---- Footer ----
 
 
115
  st.markdown("---")
116
- st.markdown(
117
- "<p style='text-align: center;'>Built with ❀️ using Streamlit & TensorFlow | By <b>Anam Jafar</b></p>",
118
- unsafe_allow_html=True
119
- )
 
1
+ # -------------------------
2
+ # Handwritten Digit Recognition App (robust preprocessing)
3
+ # Built by Anam Jafar
4
+ # -------------------------
5
 
 
6
  import streamlit as st
7
  import numpy as np
8
  import cv2
 
10
  from tensorflow.keras.models import load_model
11
  from streamlit_drawable_canvas import st_canvas
12
 
13
+ # Page config
14
+ st.set_page_config(page_title="Digit Recognition App", page_icon="πŸ”’", layout="wide")
 
 
 
 
15
 
16
+ # Background (professional)
17
  st.markdown(
18
  """
19
  <style>
 
25
  unsafe_allow_html=True
26
  )
27
 
28
+ # Load model (cached)
29
  @st.cache_resource
30
  def load_cnn_model():
31
  return load_model("mnist_cnn.h5")
32
 
33
  model = load_cnn_model()
34
 
35
+ # ---------------------
36
+ # Helper: preprocess PIL file uploads
37
+ # ---------------------
38
+ def preprocess_pil_file(file_or_pil_image):
39
+ """
40
+ Accept either a file-like object from file_uploader or a PIL.Image.
41
+ Returns: preprocessed array shape (1,28,28,1), and a display PIL image (28x28)
42
+ """
43
+ if not isinstance(file_or_pil_image, Image.Image):
44
+ img = Image.open(file_or_pil_image)
45
+ else:
46
+ img = file_or_pil_image
47
+
48
+ # convert to grayscale and resize
49
+ img = img.convert('L').resize((28, 28))
50
+ arr = np.array(img).astype('float32') / 255.0 # 0..1
51
+
52
+ # auto-invert if background is white and strokes are dark (we expect digit bright on dark background)
53
+ if arr.mean() > 0.5:
54
+ arr = 1.0 - arr
55
+
56
+ # ensure shape (1,28,28,1)
57
+ arr = arr.reshape(1, 28, 28, 1).astype('float32')
58
+ return arr, Image.fromarray((arr[0,:,:,0]*255).astype('uint8'))
59
+
60
+ # ---------------------
61
+ # Helper: preprocess canvas image (RGBA or RGB)
62
+ # ---------------------
63
+ def preprocess_canvas_image(image_data):
64
+ """
65
+ image_data: HxWx4 (RGBA) or HxWx3 (RGB) numpy array from st_canvas.
66
+ Returns preprocessed array shape (1,28,28,1) and display PIL image.
67
+ """
68
+ if image_data is None:
69
+ return None, None
70
+
71
+ # If values are float [0..255] -> convert to uint8
72
+ img_uint8 = image_data.astype('uint8')
73
+
74
+ # If has alpha channel (4), drop or composite with white background
75
+ if img_uint8.shape[2] == 4:
76
+ # composite alpha over white background
77
+ alpha = img_uint8[..., 3] / 255.0
78
+ rgb = img_uint8[..., :3].astype('float32')
79
+ white = np.ones_like(rgb) * 255.0
80
+ comp = (rgb * alpha[..., None] + white * (1 - alpha[..., None])).astype('uint8')
81
+ gray = cv2.cvtColor(comp, cv2.COLOR_RGB2GRAY)
82
+ else:
83
+ gray = cv2.cvtColor(img_uint8, cv2.COLOR_RGB2GRAY)
84
+
85
+ # Resize to 28x28, normalize
86
+ small = cv2.resize(gray, (28, 28), interpolation=cv2.INTER_AREA).astype('float32') / 255.0
87
+
88
+ # auto-invert heuristic
89
+ if small.mean() > 0.5:
90
+ small = 1.0 - small
91
+
92
+ arr = small.reshape(1, 28, 28, 1).astype('float32')
93
+ display_img = Image.fromarray((small * 255).astype('uint8'))
94
+ return arr, display_img
95
+
96
+ # ---------------------
97
+ # UI: header & sidebar
98
+ # ---------------------
99
+ st.markdown("<h1 style='text-align:center;color:#0D47A1;'>πŸ”’ Handwritten Digit Recognizer</h1>", unsafe_allow_html=True)
100
+ st.write("Upload or draw a digit (0–9). The app will preprocess the image and predict the digit.")
101
  st.markdown("---")
102
 
 
103
  st.sidebar.header("πŸ“Œ Instructions")
104
  st.sidebar.info(
105
+ "β€’ Upload PNG/JPG or draw a digit. \n"
106
+ "β€’ The app auto-preprocesses (grayscale, resize, normalize, invert if needed). \n"
107
+ "β€’ Predictions show digit + confidence & probability bar chart."
108
  )
 
109
  st.sidebar.markdown("---")
110
  st.sidebar.write("πŸ‘©β€πŸ’» **About**: Built with ❀️ by **Anam Jafar**")
111
+ st.sidebar.write("[πŸ”— LinkedIn](https://www.linkedin.com/in/anam-jafar)")
112
 
113
+ # ---------------------
114
+ # FILE UPLOAD (multiple)
115
+ # ---------------------
116
  uploaded_files = st.file_uploader(
117
  "πŸ“‚ Upload digit images (single or multiple):",
118
  type=["png", "jpg", "jpeg"],
 
121
 
122
  if uploaded_files:
123
  st.subheader("πŸ“· Uploaded Images & Predictions")
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ # display in rows of up to 4 columns
126
+ max_cols = 4
127
+ for i in range(0, len(uploaded_files), max_cols):
128
+ row_files = uploaded_files[i:i+max_cols]
129
+ cols = st.columns(len(row_files))
130
+ for j, file in enumerate(row_files):
131
+ arr, display_img = preprocess_pil_file(file)
132
+ # Debug info (remove in production)
133
+ st.experimental_show({"shape": arr.shape, "min": float(arr.min()), "max": float(arr.max())}) # optional
134
+ # Predict
135
+ with st.spinner("Predicting..."):
136
+ pred = model.predict(arr)
137
+ probs = pred[0]
138
+ label = int(np.argmax(probs))
139
+ conf = float(np.max(probs))
140
+
141
+ with cols[j]:
142
+ st.image(display_img, caption=f"Pred: {label} ({conf*100:.1f}%)", use_column_width=True)
143
+ st.bar_chart(probs) # show probability distribution
144
+
145
+ # ---------------------
146
+ # DRAWING PAD
147
+ # ---------------------
148
  st.subheader("πŸ–ŒοΈ Draw your digit here:")
149
  canvas_result = st_canvas(
150
  stroke_width=12,
 
156
  key="canvas",
157
  )
158
 
159
+ if canvas_result is not None and canvas_result.image_data is not None:
160
+ arr, display_img = preprocess_canvas_image(canvas_result.image_data)
161
+ if arr is not None:
162
+ # Debug info (remove in production)
163
+ st.experimental_show({"canvas_shape": arr.shape, "min": float(arr.min()), "max": float(arr.max())})
164
+ with st.spinner("Predicting..."):
165
+ pred = model.predict(arr)
166
+ probs = pred[0]
167
+ label = int(np.argmax(probs))
168
+ conf = float(np.max(probs))
169
+
170
+ st.markdown(
171
+ f"""
172
+ <div style="padding:12px;border-radius:8px;background:#FFF3CD;text-align:center;">
173
+ <h2 style="color:#D32F2F;">🎯 Predicted Digit: {label}</h2>
174
+ <p>Confidence: {conf*100:.2f}%</p>
175
+ </div>
176
+ """,
177
+ unsafe_allow_html=True
178
+ )
179
+ st.image(display_img, caption="Preprocessed (28Γ—28) view", width=120)
180
+ st.bar_chart(probs)
181
+
182
+ # ---------------------
183
+ # Footer
184
+ # ---------------------
185
  st.markdown("---")
186
+ st.markdown("<p style='text-align:center;'>Built with ❀️ using Streamlit & TensorFlow | By <b>Anam Jafar</b></p>", unsafe_allow_html=True)