File size: 7,578 Bytes
aee8033 29f6ccc 3f0ccc8 29f6ccc af98bbd aee8033 3f0ccc8 3f6eee9 3f0ccc8 aee8033 3f0ccc8 3f6eee9 3f0ccc8 aee8033 3f0ccc8 2a11918 3f0ccc8 a2c2878 3f0ccc8 a2c2878 3f0ccc8 a2c2878 aee8033 3f0ccc8 a2c2878 2a11918 a2c2878 3f0ccc8 3f6eee9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
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("""
<style>
.stMainBlockContainer {
max-width: 70rem;
padding-bottom: 1rem;
}
</style>
""")
# π 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("<br>")
# 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("<br>")
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("<br>")
# --- 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(
"""
<div style="text-align: center; margin-bottom: 1.5rem;">
<b>Connect with me:</b> πΌ <a href="https://www.linkedin.com/in/alex-turpo/" target="_blank">LinkedIn</a> β’
π± <a href="https://github.com/iBrokeTheCode" target="_blank">GitHub</a> β’
π€ <a href="https://huggingface.co/iBrokeTheCode" target="_blank">Hugging Face</a>
</div>
""",
unsafe_allow_html=True,
)
|