NathanSegers commited on
Commit
66d71ff
ยท
1 Parent(s): 1b881b3

Add interactive HTML UI for animal image classification

Browse files

Introduces a styled web interface allowing users to upload animal images for classification directly from the browser. Enhances usability by providing image preview, classification feedback, and error handling, making the API more accessible to non-technical users.

Files changed (1) hide show
  1. app.py +194 -3
app.py CHANGED
@@ -1,5 +1,6 @@
1
  from fastapi import FastAPI, File, UploadFile
2
  from fastapi.middleware.cors import CORSMiddleware
 
3
  import numpy as np
4
  from PIL import Image
5
  from tensorflow.keras.models import load_model
@@ -19,9 +20,199 @@ app.add_middleware(
19
 
20
  ANIMALS = ['Cat', 'Dog', 'Panda'] # Animal names here, these represent the labels of the images that we trained our model on.
21
 
22
- @app.get("/")
23
- def greet_json():
24
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
 
27
  model = load_model("hf://nathansegers/masterclass-2025")
 
1
  from fastapi import FastAPI, File, UploadFile
2
  from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import HTMLResponse
4
  import numpy as np
5
  from PIL import Image
6
  from tensorflow.keras.models import load_model
 
20
 
21
  ANIMALS = ['Cat', 'Dog', 'Panda'] # Animal names here, these represent the labels of the images that we trained our model on.
22
 
23
+
24
+ @app.get("/", response_class=HTMLResponse)
25
+ def test_upload():
26
+ html_content = """
27
+ <!DOCTYPE html>
28
+ <html lang="en">
29
+ <head>
30
+ <meta charset="UTF-8">
31
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
32
+ <title>Animal Image Classifier</title>
33
+ <style>
34
+ body {
35
+ font-family: Arial, sans-serif;
36
+ max-width: 800px;
37
+ margin: 50px auto;
38
+ padding: 20px;
39
+ background-color: #f5f5f5;
40
+ }
41
+ .container {
42
+ background-color: white;
43
+ padding: 30px;
44
+ border-radius: 10px;
45
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
46
+ }
47
+ h1 {
48
+ color: #333;
49
+ text-align: center;
50
+ }
51
+ .upload-section {
52
+ margin: 20px 0;
53
+ padding: 20px;
54
+ border: 2px dashed #ccc;
55
+ border-radius: 5px;
56
+ text-align: center;
57
+ }
58
+ input[type="file"] {
59
+ margin: 10px 0;
60
+ }
61
+ button {
62
+ background-color: #4CAF50;
63
+ color: white;
64
+ padding: 10px 20px;
65
+ border: none;
66
+ border-radius: 5px;
67
+ cursor: pointer;
68
+ font-size: 16px;
69
+ }
70
+ button:hover {
71
+ background-color: #45a049;
72
+ }
73
+ button:disabled {
74
+ background-color: #cccccc;
75
+ cursor: not-allowed;
76
+ }
77
+ .preview {
78
+ margin: 20px 0;
79
+ text-align: center;
80
+ }
81
+ .preview img {
82
+ max-width: 300px;
83
+ max-height: 300px;
84
+ border-radius: 5px;
85
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
86
+ }
87
+ .result {
88
+ margin-top: 20px;
89
+ padding: 20px;
90
+ background-color: #e8f5e9;
91
+ border-radius: 5px;
92
+ text-align: center;
93
+ font-size: 20px;
94
+ font-weight: bold;
95
+ color: #2e7d32;
96
+ }
97
+ .error {
98
+ background-color: #ffebee;
99
+ color: #c62828;
100
+ }
101
+ .loading {
102
+ display: none;
103
+ margin: 20px 0;
104
+ text-align: center;
105
+ }
106
+ .spinner {
107
+ border: 4px solid #f3f3f3;
108
+ border-top: 4px solid #4CAF50;
109
+ border-radius: 50%;
110
+ width: 40px;
111
+ height: 40px;
112
+ animation: spin 1s linear infinite;
113
+ margin: 0 auto;
114
+ }
115
+ @keyframes spin {
116
+ 0% { transform: rotate(0deg); }
117
+ 100% { transform: rotate(360deg); }
118
+ }
119
+ </style>
120
+ </head>
121
+ <body>
122
+ <div class="container">
123
+ <h1>๐Ÿพ Animal Image Classifier</h1>
124
+ <p style="text-align: center; color: #666;">Upload an image of a Cat, Dog, or Panda to classify it!</p>
125
+
126
+ <div class="upload-section">
127
+ <input type="file" id="imageInput" accept="image/*">
128
+ <br>
129
+ <button onclick="uploadImage()" id="uploadBtn">Classify Image</button>
130
+ </div>
131
+
132
+ <div class="loading" id="loading">
133
+ <div class="spinner"></div>
134
+ <p>Classifying...</p>
135
+ </div>
136
+
137
+ <div class="preview" id="preview"></div>
138
+
139
+ <div id="result"></div>
140
+ </div>
141
+
142
+ <script>
143
+ let selectedFile = null;
144
+
145
+ document.getElementById('imageInput').addEventListener('change', function(e) {
146
+ const file = e.target.files[0];
147
+ if (file) {
148
+ selectedFile = file;
149
+ // Show preview
150
+ const reader = new FileReader();
151
+ reader.onload = function(e) {
152
+ document.getElementById('preview').innerHTML =
153
+ '<img src="' + e.target.result + '" alt="Preview">';
154
+ }
155
+ reader.readAsDataURL(file);
156
+ document.getElementById('result').innerHTML = '';
157
+ }
158
+ });
159
+
160
+ async function uploadImage() {
161
+ if (!selectedFile) {
162
+ alert('Please select an image first!');
163
+ return;
164
+ }
165
+
166
+ const uploadBtn = document.getElementById('uploadBtn');
167
+ const loading = document.getElementById('loading');
168
+ const resultDiv = document.getElementById('result');
169
+
170
+ // Show loading, disable button
171
+ uploadBtn.disabled = true;
172
+ loading.style.display = 'block';
173
+ resultDiv.innerHTML = '';
174
+
175
+ const formData = new FormData();
176
+ formData.append('img', selectedFile);
177
+
178
+ try {
179
+ const response = await fetch('/upload/image', {
180
+ method: 'POST',
181
+ body: formData
182
+ });
183
+
184
+ if (response.ok) {
185
+ const result = await response.text();
186
+ const animal = result.replace(/"/g, ''); // Remove quotes if present
187
+
188
+ // Display result with emoji
189
+ const emojis = {
190
+ 'Cat': '๐Ÿฑ',
191
+ 'Dog': '๐Ÿถ',
192
+ 'Panda': '๐Ÿผ'
193
+ };
194
+
195
+ resultDiv.innerHTML =
196
+ '<div class="result">Prediction: ' +
197
+ (emojis[animal] || '') + ' ' + animal + '</div>';
198
+ } else {
199
+ resultDiv.innerHTML =
200
+ '<div class="result error">Error: ' + response.status + '</div>';
201
+ }
202
+ } catch (error) {
203
+ resultDiv.innerHTML =
204
+ '<div class="result error">Error: ' + error.message + '</div>';
205
+ } finally {
206
+ // Hide loading, enable button
207
+ loading.style.display = 'none';
208
+ uploadBtn.disabled = false;
209
+ }
210
+ }
211
+ </script>
212
+ </body>
213
+ </html>
214
+ """
215
+ return HTMLResponse(content=html_content)
216
 
217
 
218
  model = load_model("hf://nathansegers/masterclass-2025")