import os
import streamlit as st
from PIL import Image
from predictor import predict_image
APP_DIR = os.path.dirname(os.path.abspath(__file__))
ASSETS_DIR = os.path.join(APP_DIR, "assets")
# 📌 PAGE SETUP
st.set_page_config(page_title="Image Classifier App", page_icon="🤖", layout="centered")
st.html("""
""")
# 📌 INITIALIZE SESSION STATE
if "uploaded_image" not in st.session_state:
st.session_state["uploaded_image"] = None
if "example_selected" not in st.session_state:
st.session_state["example_selected"] = False
if "prediction_result" not in st.session_state:
st.session_state["prediction_result"] = None
# 📌 MAIN APP LAYOUT
with st.container():
st.title(
body="🖼️ Image Classifier with CNN",
help="An interactive application to classify images into over 1000 categories.",
)
st.html("
")
# Use tabs for different sections of the app
tab_app, tab_about, tab_architecture = st.tabs(
["**App**", "**About**", "**Architecture**"]
)
# 📌 APP TAB
with tab_app:
# Create a two-column layout for the app interface
col_upload, col_results = st.columns(2, gap="large")
# 📌 IMAGE UPLOAD & EXAMPLE SELECTION
with col_upload:
st.header("Upload an Image", divider=True)
# File uploader widget
uploaded_file = st.file_uploader(
label="Drag and drop an image here or click to browse",
type=["jpg", "jpeg", "png", "webp", "avif"],
help="Maximum file size is 200MB",
key="image_uploader",
)
st.html("
")
st.subheader("Or Try an Example", divider=True)
# Segmented control for selecting example images
selected_example = st.segmented_control(
label="Categories",
options=["Animal", "Vehicle", "Object", "Building"],
default=None,
help="Select one of the pre-loaded examples",
)
st.html("
")
# --- THE SINGLE CLASSIFY BUTTON ---
classify_button = st.button(
label="Classify Image",
key="classify_btn",
type="primary",
icon="✨",
)
# --- LOGIC FOR IMAGE SELECTION & PREDICTION ---
# Clear the previous prediction result if a new input is selected
if uploaded_file or selected_example:
st.session_state.prediction_result = None
image_to_process = None
if uploaded_file:
image_to_process = Image.open(uploaded_file)
elif selected_example:
try:
img_path = os.path.join(
APP_DIR, "assets", f"{selected_example.lower()}.jpg"
)
image_to_process = Image.open(img_path)
except FileNotFoundError:
st.error(
f"Error: The example image '{selected_example.lower()}.jpg' was not found."
)
st.stop()
# 📌 PREDICTION RESULTS
with col_results:
st.header("Results", divider=True)
# Display a "get started" message if no image is selected
if not image_to_process and not st.session_state.prediction_result:
st.info("Choose an image or an example to get a prediction.")
# Display the image if one is selected
if image_to_process:
st.image(image_to_process, caption="Image to be classified")
# If the button is clicked, run the prediction logic
if classify_button and image_to_process:
with st.spinner(text="🧠 Analyzing image..."):
try:
from predictor import predict_image
predicted_label, predicted_score = predict_image(
image_to_process
)
st.session_state.prediction_result = {
"label": predicted_label.replace("_", " ").title(),
"score": predicted_score,
}
except Exception as e:
st.error(f"An error occurred during prediction: {e}")
# Display the prediction result if available in session state
if st.session_state.prediction_result:
st.metric(
label="Prediction",
value=st.session_state.prediction_result["label"],
delta=f"{st.session_state.prediction_result['score'] * 100:.2f}%",
help="The predicted category and its confidence score.",
delta_color="normal",
)
st.balloons()
elif image_to_process:
st.info("Click 'Classify Image' to see the prediction.")
# 📌 ABOUT TAB
with tab_about:
st.header("About This Project")
st.markdown("""
- This project is an **image classification app** powered by a Convolutional Neural Network (CNN).
- Simply upload an image, and the app predicts its category from **over 1,000 classes** using a pre-trained **ResNet50** model.
- Originally developed as a **multi-service ML system** (FastAPI + Redis + Streamlit), this version has been **adapted into a single Streamlit app** for lightweight, cost-effective deployment on Hugging Face Spaces.
### Model & Description
- **Model:** ResNet50 (pre-trained on the **ImageNet** dataset with 1,000+ categories).
- **Pipeline:** Images are resized, normalized, and passed to the model.
- **Output:** The app displays the **Top prediction** with confidence score.
[ResNet50](https://www.tensorflow.org/api_docs/python/tf/keras/applications/ResNet50) is widely used in both research and production, making it an excellent showcase of deep learning capabilities and transferable ML skills.
""")
with tab_architecture:
with st.expander("🛠️ View Original System Architecture"):
st.image(
image="./src/assets/architecture.jpg",
caption="Original Microservices Architecture",
)
st.markdown("""
### Original Architecture
- **FastAPI** → REST API for image processing
- **Redis** → Message broker for service communication
- **Streamlit** → Interactive web UI
- **TensorFlow** → Deep learning inference engine
- **Locust** → Load testing & benchmarking
- **Docker Compose** → Service orchestration
### Simplified Version
- **Streamlit only** → UI and model combined in a single app
- **TensorFlow (ResNet50)** → Core prediction engine
- **Docker** → Containerized for Hugging Face Spaces deployment
This evolution demonstrates the ability to design a **scalable microservices system** and also **adapt it into a lightweight single-service solution** for cost-effective demos.
""")
# 📌 FOOTER
st.divider()
st.markdown(
"""