Shad0wKillar commited on
Commit
63ef65b
·
verified ·
1 Parent(s): f98cb2d

Put some visual feedback

Browse files
Files changed (1) hide show
  1. main.py +88 -56
main.py CHANGED
@@ -9,7 +9,7 @@ from PIL import Image
9
 
10
  app = FastAPI()
11
 
12
- # Model configurations for the pre-loaded dictionary
13
  MODEL_CONFIGS = {
14
  "b1": {"repo": "Shad0wKillar/efficientnet-b1", "file": "EfficientNet_B1_20percent.pth", "features": 1280},
15
  "b3": {"repo": "Shad0wKillar/efficientnet-b3", "file": "EfficientNet_B3_20percent.pth", "features": 1536},
@@ -18,7 +18,7 @@ MODEL_CONFIGS = {
18
  }
19
 
20
  def create_model(model_type):
21
- # I matched architectures to their specific feature counts
22
  if model_type == "b1": model = torchvision.models.efficientnet_b1()
23
  elif model_type == "b3": model = torchvision.models.efficientnet_b3()
24
  elif model_type == "b5": model = torchvision.models.efficientnet_b5()
@@ -30,7 +30,7 @@ def create_model(model_type):
30
  )
31
  return model
32
 
33
- # I pre-loaded the models to avoid cold-start delays
34
  loaded_models = {}
35
  for m_type, config in MODEL_CONFIGS.items():
36
  m = create_model(m_type)
@@ -50,74 +50,96 @@ class_names = ["pizza", "steak", "sushi"]
50
 
51
  @app.get("/", response_class=HTMLResponse)
52
  async def read_root():
53
- # I styled a new custom upload area and formatted the probability output
54
  html_content = """
55
  <!DOCTYPE html>
56
  <html lang="en">
57
  <head>
58
  <meta charset="UTF-8">
59
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
60
- <title>EfficientNet Multi-Model API</title>
61
  <style>
62
- body { font-family: system-ui, sans-serif; background-color: #0b0f19; color: #e5e7eb; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; padding: 20px; }
63
- .container { background-color: #1e293b; border: 1px solid #374151; border-radius: 12px; padding: 30px; width: 100%; max-width: 450px; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); }
64
 
65
- /* Styled Select Box */
66
- select { width: 100%; padding: 12px; margin-bottom: 20px; border-radius: 8px; border: 1px solid #374151; background: #0b0f19; color: #e5e7eb; font-size: 14px; outline: none; }
 
 
 
 
 
 
 
 
 
67
 
68
- /* Attractive Upload Area */
69
  .upload-label {
70
  display: flex; flex-direction: column; align-items: center; justify-content: center;
71
- width: 100%; height: 120px; border: 2px dashed #4b5563; border-radius: 12px;
72
- cursor: pointer; transition: all 0.2s ease; margin-bottom: 20px; color: #9ca3af;
73
  }
74
- .upload-label:hover { border-color: #3b82f6; background-color: #1a2333; color: #f3f4f6; }
75
  #imageInput { display: none; }
76
 
77
- /* Run Button */
78
- button { width: 100%; padding: 12px; background: #3b82f6; color: white; font-weight: 700; cursor: pointer; border: none; border-radius: 8px; transition: background 0.2s; }
79
- button:hover { background: #2563eb; }
80
- button:disabled { background: #4b5563; cursor: not-allowed; }
81
 
82
- #preview { max-width: 100%; border-radius: 8px; display: none; margin-bottom: 20px; border: 1px solid #374151; }
 
 
 
 
 
 
 
 
 
83
 
84
- .result-box { padding: 20px; background: #0b0f19; border-radius: 8px; display: none; text-align: center; border: 1px solid #374151; }
85
- .prob-text { color: #fbbf24; font-family: monospace; font-size: 0.9rem; margin-top: 10px; line-height: 1.5; }
86
  </style>
87
  </head>
88
  <body>
89
- <div class="container">
90
- <h2 style="margin-top:0">Food Classifier</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- <label style="font-size: 12px; color: #9ca3af; display: block; margin-bottom: 5px;">Model Architecture</label>
93
- <select id="modelSelect">
94
- <option value="b1">EfficientNet-B1</option>
95
- <option value="b3">EfficientNet-B3</option>
96
- <option value="b5">EfficientNet-B5</option>
97
- <option value="b7">EfficientNet-B7</option>
98
- </select>
99
-
100
- <input type="file" id="imageInput" accept="image/*" onchange="previewImage(event)">
101
- <label for="imageInput" class="upload-label" id="dropZone">
102
- <span style="font-size: 24px; margin-bottom: 8px;">📷</span>
103
- <span id="uploadText">Click to upload image</span>
104
- </label>
105
-
106
- <img id="preview">
107
- <button onclick="testAPI()" id="runBtn">Run Prediction</button>
108
-
109
- <div class="result-box" id="resultBox">
110
- <div id="topPrediction" style="font-size: 1.8rem; color: #10b981; font-weight: 800; text-transform: uppercase;"></div>
111
- <div id="rawProbs" class="prob-text"></div>
112
  </div>
113
  </div>
114
 
115
  <script>
116
  function previewImage(event) {
117
- const reader = new FileReader();
118
  const file = event.target.files[0];
119
  if (!file) return;
120
-
121
  reader.onload = () => {
122
  const p = document.getElementById('preview');
123
  p.src = reader.result; p.style.display = 'block';
@@ -129,10 +151,17 @@ async def read_root():
129
  async function testAPI() {
130
  const file = document.getElementById('imageInput').files[0];
131
  const model = document.getElementById('modelSelect').value;
132
- if (!file) return alert("Please select an image first");
133
 
 
 
134
  const btn = document.getElementById('runBtn');
135
- btn.innerText = "Analyzing..."; btn.disabled = true;
 
 
 
 
 
136
 
137
  const formData = new FormData();
138
  formData.append("file", file);
@@ -141,21 +170,25 @@ async def read_root():
141
  const res = await fetch(`/predict?model_type=${model}`, { method: "POST", body: formData });
142
  const data = await res.json();
143
 
144
- // I handled the decimal formatting and class extraction here
145
  const entries = Object.entries(data);
146
  const best = entries.reduce((a, b) => a[1] > b[1] ? a : b);
147
 
 
148
  document.getElementById('topPrediction').innerText = best[0];
149
 
150
- // Cleaned up the probabilities display
151
- const formattedProbs = entries
152
- .map(([name, prob]) => `${name.toUpperCase()}: ${prob.toFixed(2)}`)
153
- .join(" | ");
 
154
 
155
- document.getElementById('rawProbs').innerText = formattedProbs;
156
- document.getElementById('resultBox').style.display = 'block';
157
- } catch (e) { alert("Error: " + e.message); }
158
- finally { btn.innerText = "Run Prediction"; btn.disabled = false; }
 
 
 
159
  }
160
  </script>
161
  </body>
@@ -165,7 +198,6 @@ async def read_root():
165
 
166
  @app.post("/predict")
167
  async def predict(model_type: str = Query("b1"), file: UploadFile = File(...)):
168
- # I kept the routing logic the same for speed
169
  if model_type not in loaded_models:
170
  return {"error": "Model not found"}
171
 
 
9
 
10
  app = FastAPI()
11
 
12
+ # Model configurations sourced from the setup in EfficientNet_TransferLearned.zip
13
  MODEL_CONFIGS = {
14
  "b1": {"repo": "Shad0wKillar/efficientnet-b1", "file": "EfficientNet_B1_20percent.pth", "features": 1280},
15
  "b3": {"repo": "Shad0wKillar/efficientnet-b3", "file": "EfficientNet_B3_20percent.pth", "features": 1536},
 
18
  }
19
 
20
  def create_model(model_type):
21
+ # Architecture mapping for the 4 versions
22
  if model_type == "b1": model = torchvision.models.efficientnet_b1()
23
  elif model_type == "b3": model = torchvision.models.efficientnet_b3()
24
  elif model_type == "b5": model = torchvision.models.efficientnet_b5()
 
30
  )
31
  return model
32
 
33
+ # Pre-loading for high-speed inference on LightBox
34
  loaded_models = {}
35
  for m_type, config in MODEL_CONFIGS.items():
36
  m = create_model(m_type)
 
50
 
51
  @app.get("/", response_class=HTMLResponse)
52
  async def read_root():
53
+ # I implemented a flexbox layout to divide the screen vertically
54
  html_content = """
55
  <!DOCTYPE html>
56
  <html lang="en">
57
  <head>
58
  <meta charset="UTF-8">
59
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
60
+ <title>EfficientNet AI - MultiModel</title>
61
  <style>
62
+ :root { --bg: #0b0f19; --card: #1e293b; --accent: #3b82f6; --success: #10b981; --amber: #fbbf24; }
63
+ body, html { margin: 0; padding: 0; height: 100%; font-family: system-ui, sans-serif; background-color: var(--bg); color: #e5e7eb; overflow: hidden; }
64
 
65
+ .split-container { display: flex; height: 100vh; width: 100vw; }
66
+
67
+ /* Left Panel: Inputs */
68
+ .left-panel { flex: 1; padding: 40px; display: flex; flex-direction: column; justify-content: center; border-right: 1px solid #374151; background: #0f172a; }
69
+
70
+ /* Right Panel: Results */
71
+ .right-panel { flex: 1.2; display: flex; align-items: center; justify-content: center; background-color: var(--bg); position: relative; }
72
+
73
+ .content-width { max-width: 400px; width: 100%; margin: 0 auto; }
74
+
75
+ select, button { width: 100%; padding: 14px; margin-bottom: 20px; border-radius: 10px; border: 1px solid #374151; background: var(--bg); color: white; font-size: 15px; outline: none; }
76
 
 
77
  .upload-label {
78
  display: flex; flex-direction: column; align-items: center; justify-content: center;
79
+ width: 100%; height: 150px; border: 2px dashed #4b5563; border-radius: 15px;
80
+ cursor: pointer; transition: 0.3s; margin-bottom: 20px; background: #1e293b44;
81
  }
82
+ .upload-label:hover { border-color: var(--accent); background: #1e293b88; }
83
  #imageInput { display: none; }
84
 
85
+ button { background: var(--accent); font-weight: 700; border: none; transition: 0.2s; }
86
+ button:hover { background: #2563eb; transform: translateY(-1px); }
87
+ button:disabled { background: #4b5563; opacity: 0.6; }
 
88
 
89
+ #preview { width: 100%; border-radius: 12px; display: none; margin-bottom: 20px; border: 1px solid #374151; object-fit: cover; height: 200px; }
90
+
91
+ /* Results Styling */
92
+ .result-display { text-align: center; width: 80%; opacity: 0; transform: translateY(20px); transition: 0.5s ease-out; }
93
+ .result-display.show { opacity: 1; transform: translateY(0); }
94
+
95
+ .placeholder-text { color: #4b5563; font-size: 1.2rem; font-style: italic; }
96
+ .prediction-title { font-size: 4rem; font-weight: 900; color: var(--success); text-transform: uppercase; letter-spacing: -2px; margin: 0; }
97
+ .prob-row { display: flex; justify-content: center; gap: 15px; margin-top: 20px; }
98
+ .prob-pill { background: #1e293b; padding: 8px 15px; border-radius: 20px; border: 1px solid #374151; color: var(--amber); font-family: monospace; font-weight: bold; }
99
 
100
+ @keyframes pulse { 0% { opacity: 0.5; } 50% { opacity: 1; } 100% { opacity: 0.5; } }
101
+ .loading { animation: pulse 1s infinite; color: var(--accent); font-size: 1.5rem; font-weight: bold; }
102
  </style>
103
  </head>
104
  <body>
105
+ <div class="split-container">
106
+ <div class="left-panel">
107
+ <div class="content-width">
108
+ <h2 style="margin: 0 0 10px 0; font-size: 2rem;">Classifier</h2>
109
+ <p style="color: #9ca3af; margin-bottom: 30px;">Select a model and upload an image to begin.</p>
110
+
111
+ <select id="modelSelect">
112
+ <option value="b1">EfficientNet-B1 (Fast)</option>
113
+ <option value="b3">EfficientNet-B3</option>
114
+ <option value="b5">EfficientNet-B5</option>
115
+ <option value="b7">EfficientNet-B7 (Max Accuracy)</option>
116
+ </select>
117
+
118
+ <input type="file" id="imageInput" accept="image/*" onchange="previewImage(event)">
119
+ <label for="imageInput" class="upload-label">
120
+ <span style="font-size: 32px; margin-bottom: 10px;">📤</span>
121
+ <span id="uploadText">Drop or click to upload</span>
122
+ </label>
123
+
124
+ <img id="preview">
125
+ <button onclick="testAPI()" id="runBtn">Run Analysis</button>
126
+ </div>
127
+ </div>
128
 
129
+ <div class="right-panel" id="resultContainer">
130
+ <div class="placeholder-text" id="statusMsg">Ready for Prediction...</div>
131
+ <div class="result-display" id="resultDisplay">
132
+ <div class="prediction-title" id="topPrediction"></div>
133
+ <div class="prob-row" id="probList"></div>
134
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  </div>
136
  </div>
137
 
138
  <script>
139
  function previewImage(event) {
 
140
  const file = event.target.files[0];
141
  if (!file) return;
142
+ const reader = new FileReader();
143
  reader.onload = () => {
144
  const p = document.getElementById('preview');
145
  p.src = reader.result; p.style.display = 'block';
 
151
  async function testAPI() {
152
  const file = document.getElementById('imageInput').files[0];
153
  const model = document.getElementById('modelSelect').value;
154
+ if (!file) return alert("Please select an image.");
155
 
156
+ const statusMsg = document.getElementById('statusMsg');
157
+ const resultDisplay = document.getElementById('resultDisplay');
158
  const btn = document.getElementById('runBtn');
159
+
160
+ // I added a loading state to the right panel for instant feedback
161
+ resultDisplay.classList.remove('show');
162
+ statusMsg.innerHTML = '<div class="loading">ANALYZING...</div>';
163
+ statusMsg.style.display = 'block';
164
+ btn.disabled = true;
165
 
166
  const formData = new FormData();
167
  formData.append("file", file);
 
170
  const res = await fetch(`/predict?model_type=${model}`, { method: "POST", body: formData });
171
  const data = await res.json();
172
 
 
173
  const entries = Object.entries(data);
174
  const best = entries.reduce((a, b) => a[1] > b[1] ? a : b);
175
 
176
+ // Update UI components
177
  document.getElementById('topPrediction').innerText = best[0];
178
 
179
+ // I used .toFixed(2) to clean up the overflow issue seen in image_3ac61a.png
180
+ const list = document.getElementById('probList');
181
+ list.innerHTML = entries.map(([name, prob]) => `
182
+ <div class="prob-pill">${name.toUpperCase()}: ${prob.toFixed(2)}</div>
183
+ `).join("");
184
 
185
+ statusMsg.style.display = 'none';
186
+ resultDisplay.classList.add('show');
187
+ } catch (e) {
188
+ statusMsg.innerText = "Error during analysis.";
189
+ } finally {
190
+ btn.disabled = false;
191
+ }
192
  }
193
  </script>
194
  </body>
 
198
 
199
  @app.post("/predict")
200
  async def predict(model_type: str = Query("b1"), file: UploadFile = File(...)):
 
201
  if model_type not in loaded_models:
202
  return {"error": "Model not found"}
203