File size: 6,622 Bytes
d7c8166
 
f02a5fd
 
 
 
 
 
 
 
 
 
 
ae692d1
f02a5fd
 
 
 
 
 
 
d7c8166
dd8438e
0e07292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d7c8166
0e07292
 
 
 
 
 
 
dd8438e
0e07292
 
dd8438e
0e07292
 
 
 
 
 
 
 
 
 
dd8438e
 
 
 
f02a5fd
dd8438e
0e07292
dd8438e
0e07292
f02a5fd
 
 
 
 
 
0e07292
dd8438e
f02a5fd
dd8438e
 
f02a5fd
0e07292
 
 
 
dd8438e
0e07292
 
 
dd8438e
0e07292
 
dd8438e
0e07292
dd8438e
 
 
f02a5fd
dd8438e
0e07292
 
dd8438e
 
0e07292
 
f02a5fd
dd8438e
 
 
 
0e07292
dd8438e
 
 
0e07292
 
 
 
 
 
f02a5fd
 
dd8438e
 
 
 
 
0e07292
dd8438e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f02a5fd
 
ae692d1
 
dd8438e
 
 
 
 
 
 
0e07292
dd8438e
 
 
0e07292
 
dd8438e
0e07292
 
d7c8166
dd8438e
d7c8166
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
import gradio as gr

# πŸ“Œ CUSTOM CSS
css_code = """
#footer-container {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    background-color: var(--background-fill-primary);
    padding: var(--spacing-md);
    border-top: 1px solid var(--border-color-primary);
    text-align: center;
}

.gradio-container {
    padding-bottom: 70px !important;
}
"""


# πŸ“Œ FUNCTIONS
def predict(mode, text, image_path):
    """
    This placeholder function now returns a dictionary
    in the format expected by the gr.Label component.
    """
    multimodal_output = {
        "abcat0100000": 0.05,
        "abcat0200000": 0.10,
        "abcat0300000": 0.20,
        "abcat0400000": 0.45,
        "abcat0500000": 0.20,
    }
    text_only_output = {
        "abcat0100000": 0.08,
        "abcat0200000": 0.15,
        "abcat0300000": 0.25,
        "abcat0400000": 0.35,
        "abcat0500000": 0.17,
    }
    image_only_output = {
        "abcat0100000": 0.10,
        "abcat0200000": 0.20,
        "abcat0300000": 0.30,
        "abcat0400000": 0.25,
        "abcat0500000": 0.15,
    }

    if mode == "Multimodal":
        return multimodal_output
    elif mode == "Text Only":
        return text_only_output
    elif mode == "Image Only":
        return image_only_output
    else:
        return {}


def update_inputs(mode: str):
    if mode == "Multimodal":
        return gr.Textbox(visible=True), gr.Image(visible=True)
    elif mode == "Text Only":
        return gr.Textbox(visible=True), gr.Image(visible=False)
    elif mode == "Image Only":
        return gr.Textbox(visible=False), gr.Image(visible=True)
    else:  # Default case
        return gr.Textbox(visible=True), gr.Image(visible=True)


# πŸ“Œ USER INTERFACE
with gr.Blocks(
    title="Multimodal Product Classification",
    theme=gr.themes.Ocean(),
    css=css_code,
) as demo:
    with gr.Tabs():
        # πŸ“Œ APP TAB
        with gr.TabItem("App"):
            gr.Markdown("""
                <div style="text-align: center;">
                    <h1>πŸ›οΈ Multimodal Product Classification</h1>
                </div>
                <br><br>
                """)

            with gr.Row(equal_height=True):
                # πŸ“Œ CLASSIFICATION INPUTS COLUMN
                with gr.Column():
                    with gr.Column():
                        gr.Markdown("## πŸ“ Classification Inputs")

                        mode_radio = gr.Radio(
                            choices=["Multimodal", "Text Only", "Image Only"],
                            value="Multimodal",
                            label="Choose Classification Mode:",
                        )

                        text_input = gr.Textbox(
                            label="Product Description:",
                            placeholder="e.g., Apple iPhone 15 Pro Max 256GB",
                        )

                        image_input = gr.Image(
                            label="Product Image",
                            type="filepath",
                            visible=True,
                            height=350,
                            width="100%",
                        )

                        classify_button = gr.Button(
                            "✨ Classify Product", variant="primary"
                        )

                # πŸ“Œ RESULTS COLUMN
                with gr.Column():
                    with gr.Column():
                        gr.Markdown("## πŸ“Š Results")

                        gr.Markdown(
                            """**πŸ’‘ How to use this app**

                            This app classifies a product based on its description and image.
                            - **Multimodal:** Uses both text and image for the most accurate prediction.
                            - **Text Only:** Uses only the product description.
                            - **Image Only:** Uses only the product image.
                            """
                        )

                        gr.HTML("<hr>")

                        output_label = gr.Label(
                            label="Predict category", num_top_classes=5
                        )

        # πŸ“Œ ABOUT TAB
        with gr.TabItem("About"):
            gr.Markdown("""
## About This Project

- 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 is widely used in both research and production, making it an excellent showcase of deep learning capabilities and transferable ML skills.
""")

        # πŸ“Œ MODEL TAB
        with gr.TabItem("Model"):
            gr.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
    # gr.HTML("<hr>")
    with gr.Row(elem_id="footer-container"):
        gr.HTML("""
<div>
        <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>
""")

    # πŸ“Œ EVENT LISTENERS
    mode_radio.change(
        fn=update_inputs,
        inputs=mode_radio,
        outputs=[text_input, image_input],
    )

    classify_button.click(
        fn=predict, inputs=[mode_radio, text_input, image_input], outputs=output_label
    )


demo.launch()