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,
)