# ------------------------- # Handwritten Digit Recognition App (robust preprocessing) # Built by Anam Jafar # ------------------------- import streamlit as st import numpy as np import cv2 from PIL import Image from tensorflow.keras.models import load_model from streamlit_drawable_canvas import st_canvas # Page config st.set_page_config(page_title="Digit Recognition App", page_icon="🔢", layout="wide") # Background (professional) st.markdown( """ """, unsafe_allow_html=True ) # Load model (cached) @st.cache_resource def load_cnn_model(): return load_model("mnist_cnn.h5") model = load_cnn_model() # --------------------- # Helper: preprocess PIL file uploads # --------------------- def preprocess_pil_file(file_or_pil_image): """ Accept either a file-like object from file_uploader or a PIL.Image. Returns: preprocessed array shape (1,28,28,1), and a display PIL image (28x28) """ if not isinstance(file_or_pil_image, Image.Image): img = Image.open(file_or_pil_image) else: img = file_or_pil_image # convert to grayscale and resize img = img.convert('L').resize((28, 28)) arr = np.array(img).astype('float32') / 255.0 # 0..1 # auto-invert if background is white and strokes are dark (we expect digit bright on dark background) if arr.mean() > 0.5: arr = 1.0 - arr # ensure shape (1,28,28,1) arr = arr.reshape(1, 28, 28, 1).astype('float32') return arr, Image.fromarray((arr[0,:,:,0]*255).astype('uint8')) # --------------------- # Helper: preprocess canvas image (RGBA or RGB) # --------------------- def preprocess_canvas_image(image_data): """ image_data: HxWx4 (RGBA) or HxWx3 (RGB) numpy array from st_canvas. Returns preprocessed array shape (1,28,28,1) and display PIL image. """ if image_data is None: return None, None # If values are float [0..255] -> convert to uint8 img_uint8 = image_data.astype('uint8') # If has alpha channel (4), drop or composite with white background if img_uint8.shape[2] == 4: # composite alpha over white background alpha = img_uint8[..., 3] / 255.0 rgb = img_uint8[..., :3].astype('float32') white = np.ones_like(rgb) * 255.0 comp = (rgb * alpha[..., None] + white * (1 - alpha[..., None])).astype('uint8') gray = cv2.cvtColor(comp, cv2.COLOR_RGB2GRAY) else: gray = cv2.cvtColor(img_uint8, cv2.COLOR_RGB2GRAY) # Resize to 28x28, normalize small = cv2.resize(gray, (28, 28), interpolation=cv2.INTER_AREA).astype('float32') / 255.0 # auto-invert heuristic if small.mean() > 0.5: small = 1.0 - small arr = small.reshape(1, 28, 28, 1).astype('float32') display_img = Image.fromarray((small * 255).astype('uint8')) return arr, display_img # --------------------- # UI: header & sidebar # --------------------- st.markdown("
Confidence: {conf*100:.2f}%
Built with ❤️ using Streamlit & TensorFlow | By Anam Jafar
", unsafe_allow_html=True)