digit-generator / src /streamlit_app.py
bisryy's picture
Update src/streamlit_app.py
4d02fbe verified
Raw
History Blame Contribute Delete
4.61 kB
import streamlit as st
import torch
import torch.nn as nn
import numpy as np
from PIL import Image
# --- Configuration ---
# These must match the training script parameters
LATENT_DIM = 100
N_CLASSES = 10
IMG_SIZE = 28
CHANNELS = 1
# Use CPU for inference as the deployment environment may not have a GPU
DEVICE = torch.device('cpu')
# --- Model Architecture ---
# The model class must be defined exactly as it was during training
# so that we can load the saved weights (state_dict).
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.label_embedding = nn.Embedding(N_CLASSES, N_CLASSES)
self.model = nn.Sequential(
nn.Linear(LATENT_DIM + N_CLASSES, 256),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(256, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 1024),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(1024, IMG_SIZE * IMG_SIZE * CHANNELS),
nn.Tanh()
)
def forward(self, z, labels):
label_emb = self.label_embedding(labels)
gen_input = torch.cat((z, label_emb), -1)
img = self.model(gen_input)
img = img.view(img.size(0), CHANNELS, IMG_SIZE, IMG_SIZE)
return img
# --- Helper Function to Load the Model ---
# Use st.cache_resource to load the model only once
@st.cache_resource
def load_model(model_path):
"""Loads the pre-trained generator model."""
model = Generator().to(DEVICE)
# Load the state dictionary. map_location ensures it loads on CPU.
model.load_state_dict(torch.load(model_path, map_location=DEVICE))
model.eval() # Set the model to evaluation mode
return model
# --- Image Generation Function ---
def generate_images(model, digit_to_generate, num_images=5):
"""Generates a specified number of images for a given digit."""
with torch.no_grad():
# Create random noise vectors (latent space)
z = torch.randn(num_images, LATENT_DIM, device=DEVICE)
# Create labels for the desired digit
labels = torch.LongTensor([digit_to_generate] * num_images).to(DEVICE)
# Generate images
generated_imgs_tensor = model(z, labels)
# Post-process images for display
# 1. Move to CPU and convert to numpy
# 2. Denormalize from [-1, 1] to [0, 1]
# 3. Reshape from (N, C, H, W) to (N, H, W)
generated_imgs_np = generated_imgs_tensor.cpu().numpy()
generated_imgs_np = 0.5 * generated_imgs_np + 0.5 # Denormalize
generated_imgs_np = generated_imgs_np.squeeze() # Remove channel dim
return generated_imgs_np
# --- Streamlit Web App UI ---
st.set_page_config(page_title="Digit Generator", layout="wide")
st.title("✍️ Handwritten Digit Generator")
st.write(
"This web app uses a Conditional Generative Adversarial Network (cGAN) "
"trained on the MNIST dataset to generate new images of handwritten digits. "
"Select a digit from the sidebar and click 'Generate'!"
)
# --- Sidebar Controls ---
st.sidebar.header("Controls")
digit_to_generate = st.sidebar.selectbox(
"Select a digit (0-9):",
options=list(range(10))
)
generate_button = st.sidebar.button("Generate Images", type="primary")
# --- Main Page Display ---
if generate_button:
# Load the generator model
try:
generator = load_model("src/cgan_generator.pth")
st.subheader(f"Generating 5 images for the digit: {digit_to_generate}")
with st.spinner("🧠 Model is thinking..."):
# Generate the images
images_to_display = generate_images(generator, digit_to_generate, num_images=5)
# Create 5 columns to display images side-by-side
cols = st.columns(5)
for i, image_array in enumerate(images_to_display):
with cols[i]:
st.image(
image_array,
caption=f"Generated Image {i+1}",
width=150, # Control the display size
use_column_width='auto'
)
st.success("Done!")
except FileNotFoundError:
st.error(
"Model file 'cgan_generator.pth' not found. "
"Please make sure the trained model file is in the same directory as this script."
)
except Exception as e:
st.error(f"An error occurred: {e}")
else:
st.info("Select a digit and click the 'Generate Images' button in the sidebar to start.")