arcsu1 commited on
Commit
55ed626
·
1 Parent(s): 9d26d42

Add web interface for face generation

Browse files
Files changed (3) hide show
  1. README.md +7 -3
  2. app.py +6 -1
  3. templates/index.html +405 -0
README.md CHANGED
@@ -26,8 +26,9 @@ pip install -r requirements.txt
26
  python app.py
27
  ```
28
 
29
- 3. Access the API:
30
- - API: http://localhost:8002
 
31
  - Health check: http://localhost:8002/health
32
 
33
  ### Docker
@@ -44,7 +45,10 @@ docker run -p 8002:8002 facegen-api
44
 
45
  ## API Endpoints
46
 
47
- ### GET `/`
 
 
 
48
  Health check and info
49
  ```bash
50
  curl http://localhost:8002/
 
26
  python app.py
27
  ```
28
 
29
+ 3. Access the application:
30
+ - **Web Interface**: http://localhost:8002 (Try it directly in your browser!)
31
+ - API Info: http://localhost:8002/api
32
  - Health check: http://localhost:8002/health
33
 
34
  ### Docker
 
45
 
46
  ## API Endpoints
47
 
48
+ ### Web Interface
49
+ Visit the root URL to access the interactive web interface where you can generate faces with a user-friendly UI.
50
+
51
+ ### GET `/api`
52
  Health check and info
53
  ```bash
54
  curl http://localhost:8002/
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, jsonify, request, send_file
2
  from flask_cors import CORS
3
  import numpy as np
4
  from keras.models import load_model
@@ -28,6 +28,11 @@ def load_gan_model():
28
  load_gan_model()
29
 
30
  @app.route("/")
 
 
 
 
 
31
  def root():
32
  return jsonify({
33
  "message": "Face Generator API",
 
1
+ from flask import Flask, jsonify, request, send_file, render_template
2
  from flask_cors import CORS
3
  import numpy as np
4
  from keras.models import load_model
 
28
  load_gan_model()
29
 
30
  @app.route("/")
31
+ def index():
32
+ """Serve the web interface"""
33
+ return render_template('index.html')
34
+
35
+ @app.route("/api")
36
  def root():
37
  return jsonify({
38
  "message": "Face Generator API",
templates/index.html ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Face Generator - Create AI Faces</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 900px;
23
+ margin: 0 auto;
24
+ }
25
+
26
+ header {
27
+ text-align: center;
28
+ color: white;
29
+ margin-bottom: 40px;
30
+ }
31
+
32
+ h1 {
33
+ font-size: 2.5rem;
34
+ font-weight: 700;
35
+ margin-bottom: 10px;
36
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
37
+ }
38
+
39
+ .subtitle {
40
+ font-size: 1.1rem;
41
+ opacity: 0.9;
42
+ }
43
+
44
+ .main-card {
45
+ background: white;
46
+ border-radius: 16px;
47
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
48
+ padding: 40px;
49
+ }
50
+
51
+ .controls {
52
+ margin-bottom: 30px;
53
+ }
54
+
55
+ .control-group {
56
+ margin-bottom: 25px;
57
+ }
58
+
59
+ label {
60
+ display: block;
61
+ font-weight: 600;
62
+ color: #2d3748;
63
+ margin-bottom: 8px;
64
+ font-size: 0.95rem;
65
+ }
66
+
67
+ .slider-container {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 15px;
71
+ }
72
+
73
+ input[type="range"] {
74
+ flex: 1;
75
+ height: 6px;
76
+ border-radius: 3px;
77
+ background: #e2e8f0;
78
+ outline: none;
79
+ -webkit-appearance: none;
80
+ }
81
+
82
+ input[type="range"]::-webkit-slider-thumb {
83
+ -webkit-appearance: none;
84
+ appearance: none;
85
+ width: 20px;
86
+ height: 20px;
87
+ border-radius: 50%;
88
+ background: #667eea;
89
+ cursor: pointer;
90
+ transition: all 0.2s;
91
+ }
92
+
93
+ input[type="range"]::-webkit-slider-thumb:hover {
94
+ transform: scale(1.2);
95
+ background: #5a67d8;
96
+ }
97
+
98
+ input[type="range"]::-moz-range-thumb {
99
+ width: 20px;
100
+ height: 20px;
101
+ border-radius: 50%;
102
+ background: #667eea;
103
+ cursor: pointer;
104
+ border: none;
105
+ }
106
+
107
+ .value-display {
108
+ min-width: 40px;
109
+ text-align: center;
110
+ font-weight: 600;
111
+ color: #667eea;
112
+ font-size: 1.1rem;
113
+ }
114
+
115
+ input[type="number"] {
116
+ width: 100%;
117
+ padding: 12px;
118
+ border: 2px solid #e2e8f0;
119
+ border-radius: 8px;
120
+ font-size: 1rem;
121
+ transition: border-color 0.2s;
122
+ }
123
+
124
+ input[type="number"]:focus {
125
+ outline: none;
126
+ border-color: #667eea;
127
+ }
128
+
129
+ .button-group {
130
+ display: flex;
131
+ gap: 12px;
132
+ margin-top: 25px;
133
+ }
134
+
135
+ button {
136
+ flex: 1;
137
+ padding: 14px 28px;
138
+ font-size: 1rem;
139
+ font-weight: 600;
140
+ border: none;
141
+ border-radius: 8px;
142
+ cursor: pointer;
143
+ transition: all 0.2s;
144
+ }
145
+
146
+ .btn-primary {
147
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
148
+ color: white;
149
+ }
150
+
151
+ .btn-primary:hover {
152
+ transform: translateY(-2px);
153
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
154
+ }
155
+
156
+ .btn-primary:active {
157
+ transform: translateY(0);
158
+ }
159
+
160
+ .btn-primary:disabled {
161
+ opacity: 0.6;
162
+ cursor: not-allowed;
163
+ transform: none;
164
+ }
165
+
166
+ .btn-secondary {
167
+ background: white;
168
+ color: #667eea;
169
+ border: 2px solid #667eea;
170
+ }
171
+
172
+ .btn-secondary:hover {
173
+ background: #f7fafc;
174
+ }
175
+
176
+ .result-area {
177
+ margin-top: 35px;
178
+ text-align: center;
179
+ }
180
+
181
+ .result-area h3 {
182
+ color: #2d3748;
183
+ margin-bottom: 20px;
184
+ font-size: 1.3rem;
185
+ }
186
+
187
+ #resultImage {
188
+ max-width: 100%;
189
+ border-radius: 12px;
190
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
191
+ display: none;
192
+ }
193
+
194
+ #resultImage.show {
195
+ display: block;
196
+ animation: fadeIn 0.5s;
197
+ }
198
+
199
+ @keyframes fadeIn {
200
+ from { opacity: 0; transform: translateY(20px); }
201
+ to { opacity: 1; transform: translateY(0); }
202
+ }
203
+
204
+ .loading {
205
+ display: none;
206
+ color: #667eea;
207
+ font-weight: 600;
208
+ margin-top: 20px;
209
+ }
210
+
211
+ .loading.show {
212
+ display: block;
213
+ }
214
+
215
+ .spinner {
216
+ border: 3px solid #f3f4f6;
217
+ border-top: 3px solid #667eea;
218
+ border-radius: 50%;
219
+ width: 40px;
220
+ height: 40px;
221
+ animation: spin 1s linear infinite;
222
+ margin: 20px auto;
223
+ }
224
+
225
+ @keyframes spin {
226
+ 0% { transform: rotate(0deg); }
227
+ 100% { transform: rotate(360deg); }
228
+ }
229
+
230
+ .error {
231
+ background: #fee;
232
+ color: #c53030;
233
+ padding: 12px;
234
+ border-radius: 8px;
235
+ margin-top: 15px;
236
+ display: none;
237
+ }
238
+
239
+ .error.show {
240
+ display: block;
241
+ }
242
+
243
+ footer {
244
+ text-align: center;
245
+ color: white;
246
+ margin-top: 40px;
247
+ opacity: 0.8;
248
+ }
249
+
250
+ .info-box {
251
+ background: #f7fafc;
252
+ padding: 15px;
253
+ border-radius: 8px;
254
+ margin-bottom: 25px;
255
+ border-left: 4px solid #667eea;
256
+ }
257
+
258
+ .info-box p {
259
+ color: #4a5568;
260
+ line-height: 1.6;
261
+ margin: 0;
262
+ }
263
+
264
+ @media (max-width: 640px) {
265
+ h1 {
266
+ font-size: 2rem;
267
+ }
268
+
269
+ .main-card {
270
+ padding: 25px;
271
+ }
272
+
273
+ .button-group {
274
+ flex-direction: column;
275
+ }
276
+ }
277
+ </style>
278
+ </head>
279
+ <body>
280
+ <div class="container">
281
+ <header>
282
+ <h1>🎨 Face Generator</h1>
283
+ <p class="subtitle">Generate realistic faces using AI</p>
284
+ </header>
285
+
286
+ <div class="main-card">
287
+ <div class="info-box">
288
+ <p>Create synthetic face images using a trained GAN model. Adjust the number of faces and seed for different results.</p>
289
+ </div>
290
+
291
+ <div class="controls">
292
+ <div class="control-group">
293
+ <label for="numFaces">Number of Faces (1-16)</label>
294
+ <div class="slider-container">
295
+ <input type="range" id="numFaces" min="1" max="16" value="1">
296
+ <span class="value-display" id="numFacesValue">1</span>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="control-group">
301
+ <label for="seed">Random Seed (optional)</label>
302
+ <input type="number" id="seed" placeholder="Leave empty for random generation">
303
+ </div>
304
+
305
+ <div class="button-group">
306
+ <button class="btn-primary" id="generateBtn">Generate Faces</button>
307
+ <button class="btn-secondary" id="randomBtn">Surprise Me!</button>
308
+ </div>
309
+ </div>
310
+
311
+ <div class="result-area">
312
+ <div class="loading" id="loading">
313
+ <div class="spinner"></div>
314
+ <p>Generating faces...</p>
315
+ </div>
316
+
317
+ <div class="error" id="error"></div>
318
+
319
+ <div id="resultContainer">
320
+ <h3 id="resultTitle" style="display: none;">Generated Result</h3>
321
+ <img id="resultImage" alt="Generated faces">
322
+ </div>
323
+ </div>
324
+ </div>
325
+
326
+ <footer>
327
+ <p>Powered by GAN Technology</p>
328
+ </footer>
329
+ </div>
330
+
331
+ <script>
332
+ const numFacesInput = document.getElementById('numFaces');
333
+ const numFacesValue = document.getElementById('numFacesValue');
334
+ const seedInput = document.getElementById('seed');
335
+ const generateBtn = document.getElementById('generateBtn');
336
+ const randomBtn = document.getElementById('randomBtn');
337
+ const resultImage = document.getElementById('resultImage');
338
+ const resultTitle = document.getElementById('resultTitle');
339
+ const loading = document.getElementById('loading');
340
+ const errorDiv = document.getElementById('error');
341
+
342
+ numFacesInput.addEventListener('input', (e) => {
343
+ numFacesValue.textContent = e.target.value;
344
+ });
345
+
346
+ randomBtn.addEventListener('click', () => {
347
+ numFacesInput.value = Math.floor(Math.random() * 9) + 1;
348
+ numFacesValue.textContent = numFacesInput.value;
349
+ seedInput.value = '';
350
+ generateFaces();
351
+ });
352
+
353
+ generateBtn.addEventListener('click', generateFaces);
354
+
355
+ async function generateFaces() {
356
+ const numFaces = parseInt(numFacesInput.value);
357
+ const seed = seedInput.value ? parseInt(seedInput.value) : null;
358
+
359
+ loading.classList.add('show');
360
+ errorDiv.classList.remove('show');
361
+ resultImage.classList.remove('show');
362
+ resultTitle.style.display = 'none';
363
+ generateBtn.disabled = true;
364
+ randomBtn.disabled = true;
365
+
366
+ try {
367
+ const payload = {
368
+ n_samples: numFaces
369
+ };
370
+
371
+ if (seed !== null) {
372
+ payload.seed = seed;
373
+ }
374
+
375
+ const response = await fetch('/generate', {
376
+ method: 'POST',
377
+ headers: {
378
+ 'Content-Type': 'application/json'
379
+ },
380
+ body: JSON.stringify(payload)
381
+ });
382
+
383
+ if (!response.ok) {
384
+ throw new Error('Failed to generate faces');
385
+ }
386
+
387
+ const blob = await response.blob();
388
+ const imageUrl = URL.createObjectURL(blob);
389
+
390
+ resultImage.src = imageUrl;
391
+ resultImage.classList.add('show');
392
+ resultTitle.style.display = 'block';
393
+
394
+ } catch (error) {
395
+ errorDiv.textContent = 'Failed to generate faces. Please try again.';
396
+ errorDiv.classList.add('show');
397
+ } finally {
398
+ loading.classList.remove('show');
399
+ generateBtn.disabled = false;
400
+ randomBtn.disabled = false;
401
+ }
402
+ }
403
+ </script>
404
+ </body>
405
+ </html>