nykadamec commited on
Commit
18ea579
Β·
1 Parent(s): 3f578a8

test_api_1

Browse files
Files changed (4) hide show
  1. Dockerfile +31 -0
  2. package.json +37 -0
  3. public/index.html +279 -0
  4. server.js +247 -0
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Node.js 18 LTS as base image
2
+ FROM node:18-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies for TensorFlow.js
8
+ RUN apt-get update && apt-get install -y \
9
+ python3 \
10
+ python3-pip \
11
+ build-essential \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy package files
15
+ COPY package*.json ./
16
+
17
+ # Install Node.js dependencies
18
+ RUN npm ci --only=production
19
+
20
+ # Copy application code
21
+ COPY . .
22
+
23
+ # Expose port for Hugging Face Spaces
24
+ EXPOSE 7860
25
+
26
+ # Set environment variables
27
+ ENV NODE_ENV=production
28
+ ENV PORT=7860
29
+
30
+ # Start the server
31
+ CMD ["npm", "start"]
package.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "upscaler-api",
3
+ "version": "1.0.0",
4
+ "description": "AI Image Upscaler API for Hugging Face Spaces",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "multer": "^1.4.5-lts.1",
13
+ "cors": "^2.8.5",
14
+ "@tensorflow/tfjs": "^4.11.0",
15
+ "@tensorflow/tfjs-backend-wasm": "~4.11.0",
16
+ "@tensorflow/tfjs-backend-webgl": "~4.11.0",
17
+ "@tensorflow/tfjs-backend-cpu": "~4.11.0",
18
+ "@upscalerjs/esrgan-slim": "1.0.0-beta.12",
19
+ "@upscalerjs/esrgan-medium": "1.0.0-beta.13",
20
+ "@upscalerjs/esrgan-thick": "1.0.0-beta.16",
21
+ "upscaler": "1.0.0-beta.19",
22
+ "canvas": "^2.11.2",
23
+ "sharp": "^0.32.6"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "keywords": [
29
+ "ai",
30
+ "upscaler",
31
+ "tensorflow",
32
+ "image-processing",
33
+ "api"
34
+ ],
35
+ "author": "Upscale2 Team",
36
+ "license": "MIT"
37
+ }
public/index.html ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>AI Image Upscaler API Test</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background: #f5f5f5;
14
+ }
15
+ .container {
16
+ background: white;
17
+ padding: 30px;
18
+ border-radius: 12px;
19
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
+ }
21
+ h1 {
22
+ color: #333;
23
+ text-align: center;
24
+ margin-bottom: 30px;
25
+ }
26
+ .form-group {
27
+ margin-bottom: 20px;
28
+ }
29
+ label {
30
+ display: block;
31
+ margin-bottom: 5px;
32
+ font-weight: 600;
33
+ color: #555;
34
+ }
35
+ input, select {
36
+ width: 100%;
37
+ padding: 10px;
38
+ border: 2px solid #ddd;
39
+ border-radius: 6px;
40
+ font-size: 16px;
41
+ box-sizing: border-box;
42
+ }
43
+ input:focus, select:focus {
44
+ outline: none;
45
+ border-color: #007bff;
46
+ }
47
+ button {
48
+ background: #007bff;
49
+ color: white;
50
+ padding: 12px 24px;
51
+ border: none;
52
+ border-radius: 6px;
53
+ font-size: 16px;
54
+ cursor: pointer;
55
+ width: 100%;
56
+ margin-top: 10px;
57
+ }
58
+ button:hover {
59
+ background: #0056b3;
60
+ }
61
+ button:disabled {
62
+ background: #ccc;
63
+ cursor: not-allowed;
64
+ }
65
+ .result {
66
+ margin-top: 30px;
67
+ padding: 20px;
68
+ background: #f8f9fa;
69
+ border-radius: 6px;
70
+ border-left: 4px solid #007bff;
71
+ }
72
+ .error {
73
+ background: #f8d7da;
74
+ border-left-color: #dc3545;
75
+ color: #721c24;
76
+ }
77
+ .success {
78
+ background: #d4edda;
79
+ border-left-color: #28a745;
80
+ color: #155724;
81
+ }
82
+ .image-container {
83
+ display: flex;
84
+ gap: 20px;
85
+ margin-top: 20px;
86
+ flex-wrap: wrap;
87
+ }
88
+ .image-box {
89
+ flex: 1;
90
+ min-width: 300px;
91
+ }
92
+ .image-box h3 {
93
+ margin-top: 0;
94
+ color: #555;
95
+ }
96
+ .image-box img {
97
+ max-width: 100%;
98
+ height: auto;
99
+ border: 2px solid #ddd;
100
+ border-radius: 6px;
101
+ }
102
+ .metadata {
103
+ background: #e9ecef;
104
+ padding: 15px;
105
+ border-radius: 6px;
106
+ margin-top: 15px;
107
+ font-family: monospace;
108
+ font-size: 14px;
109
+ }
110
+ .loading {
111
+ text-align: center;
112
+ padding: 20px;
113
+ }
114
+ .spinner {
115
+ border: 4px solid #f3f3f3;
116
+ border-top: 4px solid #007bff;
117
+ border-radius: 50%;
118
+ width: 40px;
119
+ height: 40px;
120
+ animation: spin 1s linear infinite;
121
+ margin: 0 auto 10px;
122
+ }
123
+ @keyframes spin {
124
+ 0% { transform: rotate(0deg); }
125
+ 100% { transform: rotate(360deg); }
126
+ }
127
+ </style>
128
+ </head>
129
+ <body>
130
+ <div class="container">
131
+ <h1>πŸš€ AI Image Upscaler API Test</h1>
132
+
133
+ <form id="upscaleForm">
134
+ <div class="form-group">
135
+ <label for="imageFile">Select Image:</label>
136
+ <input type="file" id="imageFile" accept="image/*" required>
137
+ </div>
138
+
139
+ <div class="form-group">
140
+ <label for="scale">Scale Factor:</label>
141
+ <select id="scale">
142
+ <option value="2">2x</option>
143
+ <option value="3">3x</option>
144
+ <option value="4">4x</option>
145
+ </select>
146
+ </div>
147
+
148
+ <div class="form-group">
149
+ <label for="modelType">AI Model:</label>
150
+ <select id="modelType">
151
+ <option value="esrgan-slim">ESRGAN Slim (Fast)</option>
152
+ <option value="esrgan-medium">ESRGAN Medium (Balanced)</option>
153
+ <option value="esrgan-thick">ESRGAN Thick (Best Quality)</option>
154
+ </select>
155
+ </div>
156
+
157
+ <div class="form-group">
158
+ <label for="patchSize">Patch Size:</label>
159
+ <select id="patchSize">
160
+ <option value="64">64</option>
161
+ <option value="96">96</option>
162
+ <option value="128" selected>128</option>
163
+ <option value="160">160</option>
164
+ <option value="192">192</option>
165
+ </select>
166
+ </div>
167
+
168
+ <div class="form-group">
169
+ <label for="padding">Padding:</label>
170
+ <select id="padding">
171
+ <option value="0">0</option>
172
+ <option value="4">4</option>
173
+ <option value="8" selected>8</option>
174
+ <option value="12">12</option>
175
+ <option value="16">16</option>
176
+ </select>
177
+ </div>
178
+
179
+ <button type="submit" id="submitBtn">Upscale Image</button>
180
+ </form>
181
+
182
+ <div id="result"></div>
183
+ </div>
184
+
185
+ <script>
186
+ document.getElementById('upscaleForm').addEventListener('submit', async (e) => {
187
+ e.preventDefault();
188
+
189
+ const fileInput = document.getElementById('imageFile');
190
+ const scale = document.getElementById('scale').value;
191
+ const modelType = document.getElementById('modelType').value;
192
+ const patchSize = document.getElementById('patchSize').value;
193
+ const padding = document.getElementById('padding').value;
194
+ const submitBtn = document.getElementById('submitBtn');
195
+ const resultDiv = document.getElementById('result');
196
+
197
+ if (!fileInput.files[0]) {
198
+ alert('Please select an image file');
199
+ return;
200
+ }
201
+
202
+ // Show loading state
203
+ submitBtn.disabled = true;
204
+ submitBtn.textContent = 'Processing...';
205
+ resultDiv.innerHTML = `
206
+ <div class="loading">
207
+ <div class="spinner"></div>
208
+ <p>Upscaling image with ${scale}x scale using ${modelType} model...</p>
209
+ </div>
210
+ `;
211
+
212
+ try {
213
+ const formData = new FormData();
214
+ formData.append('image', fileInput.files[0]);
215
+ formData.append('scale', scale);
216
+ formData.append('modelType', modelType);
217
+ formData.append('patchSize', patchSize);
218
+ formData.append('padding', padding);
219
+
220
+ const startTime = Date.now();
221
+ const response = await fetch('/upscale', {
222
+ method: 'POST',
223
+ body: formData
224
+ });
225
+
226
+ const result = await response.json();
227
+ const totalTime = Date.now() - startTime;
228
+
229
+ if (result.success) {
230
+ // Create original image URL
231
+ const originalImageUrl = URL.createObjectURL(fileInput.files[0]);
232
+
233
+ resultDiv.innerHTML = `
234
+ <div class="result success">
235
+ <h3>βœ… Upscaling Successful!</h3>
236
+ <div class="image-container">
237
+ <div class="image-box">
238
+ <h3>Original Image</h3>
239
+ <img src="${originalImageUrl}" alt="Original">
240
+ </div>
241
+ <div class="image-box">
242
+ <h3>Upscaled Image (${result.metadata.scale}x)</h3>
243
+ <img src="${result.result}" alt="Upscaled">
244
+ <a href="${result.result}" download="upscaled-${result.metadata.scale}x.png"
245
+ style="display: inline-block; margin-top: 10px; padding: 8px 16px; background: #007bff; color: white; text-decoration: none; border-radius: 4px;">
246
+ Download Upscaled Image
247
+ </a>
248
+ </div>
249
+ </div>
250
+ <div class="metadata">
251
+ <strong>Processing Details:</strong><br>
252
+ Scale: ${result.metadata.scale}x<br>
253
+ Model: ${result.metadata.modelType}<br>
254
+ Patch Size: ${result.metadata.patchSize}<br>
255
+ Padding: ${result.metadata.padding}<br>
256
+ Backend: ${result.metadata.backend}<br>
257
+ Server Processing Time: ${result.metadata.processingTime}ms<br>
258
+ Total Time: ${totalTime}ms
259
+ </div>
260
+ </div>
261
+ `;
262
+ } else {
263
+ throw new Error(result.error || 'Unknown error');
264
+ }
265
+ } catch (error) {
266
+ resultDiv.innerHTML = `
267
+ <div class="result error">
268
+ <h3>❌ Error</h3>
269
+ <p><strong>Failed to upscale image:</strong> ${error.message}</p>
270
+ </div>
271
+ `;
272
+ } finally {
273
+ submitBtn.disabled = false;
274
+ submitBtn.textContent = 'Upscale Image';
275
+ }
276
+ });
277
+ </script>
278
+ </body>
279
+ </html>
server.js ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const multer = require('multer');
3
+ const cors = require('cors');
4
+ const sharp = require('sharp');
5
+ const tf = require('@tensorflow/tfjs');
6
+ require('@tensorflow/tfjs-backend-wasm');
7
+ require('@tensorflow/tfjs-backend-cpu');
8
+ const Upscaler = require('upscaler').default;
9
+
10
+ const app = express();
11
+ const PORT = process.env.PORT || 7860;
12
+
13
+ // Middleware
14
+ app.use(cors());
15
+ app.use(express.json({ limit: '50mb' }));
16
+ app.use(express.urlencoded({ extended: true, limit: '50mb' }));
17
+
18
+ // Serve static files from public directory
19
+ app.use(express.static('public'));
20
+
21
+ // Configure multer for file uploads
22
+ const upload = multer({
23
+ storage: multer.memoryStorage(),
24
+ limits: {
25
+ fileSize: 10 * 1024 * 1024, // 10MB limit
26
+ },
27
+ fileFilter: (req, file, cb) => {
28
+ if (file.mimetype.startsWith('image/')) {
29
+ cb(null, true);
30
+ } else {
31
+ cb(new Error('Only image files are allowed'), false);
32
+ }
33
+ }
34
+ });
35
+
36
+ // Global upscaler instance
37
+ let upscalerInstance = null;
38
+
39
+ // Initialize TensorFlow.js backend
40
+ async function initializeTensorFlow() {
41
+ try {
42
+ console.log('Initializing TensorFlow.js...');
43
+
44
+ // Try to set WASM backend first, fallback to CPU
45
+ try {
46
+ await tf.setBackend('wasm');
47
+ await tf.ready();
48
+ console.log('TensorFlow.js initialized with WASM backend');
49
+ } catch (wasmError) {
50
+ console.warn('WASM backend failed, falling back to CPU:', wasmError.message);
51
+ await tf.setBackend('cpu');
52
+ await tf.ready();
53
+ console.log('TensorFlow.js initialized with CPU backend');
54
+ }
55
+
56
+ console.log('Current backend:', tf.getBackend());
57
+ return true;
58
+ } catch (error) {
59
+ console.error('Failed to initialize TensorFlow.js:', error);
60
+ return false;
61
+ }
62
+ }
63
+
64
+ // Get model for scale and type
65
+ async function getModelForScaleAndType(scale, modelType) {
66
+ switch (modelType) {
67
+ case 'esrgan-slim':
68
+ const { x2: slimX2, x3: slimX3, x4: slimX4 } = require('@upscalerjs/esrgan-slim');
69
+ if (scale === 2) return slimX2;
70
+ if (scale === 3) return slimX3;
71
+ return slimX4;
72
+
73
+ case 'esrgan-medium':
74
+ const { x2: mediumX2, x3: mediumX3, x4: mediumX4 } = require('@upscalerjs/esrgan-medium');
75
+ if (scale === 2) return mediumX2;
76
+ if (scale === 3) return mediumX3;
77
+ return mediumX4;
78
+
79
+ case 'esrgan-thick':
80
+ const { x2: thickX2, x3: thickX3, x4: thickX4 } = require('@upscalerjs/esrgan-thick');
81
+ if (scale === 2) return thickX2;
82
+ if (scale === 3) return thickX3;
83
+ return thickX4;
84
+
85
+ default:
86
+ // Default to esrgan-slim
87
+ const { x2: defaultX2, x3: defaultX3, x4: defaultX4 } = require('@upscalerjs/esrgan-slim');
88
+ if (scale === 2) return defaultX2;
89
+ if (scale === 3) return defaultX3;
90
+ return defaultX4;
91
+ }
92
+ }
93
+
94
+ // Initialize upscaler with specific model
95
+ async function initializeUpscaler(scale = 2, modelType = 'esrgan-slim') {
96
+ try {
97
+ console.log(`Initializing upscaler with scale ${scale}x and model ${modelType}...`);
98
+
99
+ const model = await getModelForScaleAndType(scale, modelType);
100
+ upscalerInstance = new Upscaler({ model });
101
+
102
+ console.log('Upscaler initialized successfully');
103
+ return upscalerInstance;
104
+ } catch (error) {
105
+ console.error('Failed to initialize upscaler:', error);
106
+ throw error;
107
+ }
108
+ }
109
+
110
+ // Convert buffer to base64 data URL
111
+ function bufferToDataURL(buffer, mimeType = 'image/png') {
112
+ const base64 = buffer.toString('base64');
113
+ return `data:${mimeType};base64,${base64}`;
114
+ }
115
+
116
+ // Health check endpoint
117
+ app.get('/', (req, res) => {
118
+ res.json({
119
+ status: 'ok',
120
+ message: 'AI Image Upscaler API',
121
+ backend: tf.getBackend(),
122
+ version: '1.0.0',
123
+ endpoints: {
124
+ upscale: 'POST /upscale',
125
+ health: 'GET /'
126
+ }
127
+ });
128
+ });
129
+
130
+ // Main upscale endpoint
131
+ app.post('/upscale', upload.single('image'), async (req, res) => {
132
+ try {
133
+ if (!req.file) {
134
+ return res.status(400).json({ error: 'No image file provided' });
135
+ }
136
+
137
+ const { scale = 2, modelType = 'esrgan-slim', patchSize = 128, padding = 8 } = req.body;
138
+
139
+ // Validate parameters
140
+ const validScales = [2, 3, 4];
141
+ const validModels = ['esrgan-slim', 'esrgan-medium', 'esrgan-thick'];
142
+
143
+ if (!validScales.includes(parseInt(scale))) {
144
+ return res.status(400).json({ error: 'Invalid scale. Must be 2, 3, or 4' });
145
+ }
146
+
147
+ if (!validModels.includes(modelType)) {
148
+ return res.status(400).json({ error: 'Invalid model type' });
149
+ }
150
+
151
+ console.log(`Processing image with scale ${scale}x, model ${modelType}`);
152
+
153
+ // Initialize upscaler if needed
154
+ if (!upscalerInstance) {
155
+ await initializeUpscaler(parseInt(scale), modelType);
156
+ }
157
+
158
+ // Convert image buffer to data URL
159
+ const inputDataURL = bufferToDataURL(req.file.buffer, req.file.mimetype);
160
+
161
+ // Perform upscaling
162
+ console.log('Starting upscaling...');
163
+ const startTime = Date.now();
164
+
165
+ const result = await upscalerInstance.upscale(inputDataURL, {
166
+ output: 'base64',
167
+ patchSize: parseInt(patchSize),
168
+ padding: parseInt(padding),
169
+ awaitNextFrame: true
170
+ });
171
+
172
+ const processingTime = Date.now() - startTime;
173
+ console.log(`Upscaling completed in ${processingTime}ms`);
174
+
175
+ // Return the upscaled image
176
+ res.json({
177
+ success: true,
178
+ result: result,
179
+ metadata: {
180
+ scale: parseInt(scale),
181
+ modelType: modelType,
182
+ patchSize: parseInt(patchSize),
183
+ padding: parseInt(padding),
184
+ processingTime: processingTime,
185
+ backend: tf.getBackend()
186
+ }
187
+ });
188
+
189
+ } catch (error) {
190
+ console.error('Upscaling error:', error);
191
+ res.status(500).json({
192
+ error: 'Failed to upscale image',
193
+ message: error.message,
194
+ backend: tf.getBackend()
195
+ });
196
+ }
197
+ });
198
+
199
+ // Error handling middleware
200
+ app.use((error, req, res, next) => {
201
+ if (error instanceof multer.MulterError) {
202
+ if (error.code === 'LIMIT_FILE_SIZE') {
203
+ return res.status(400).json({ error: 'File too large. Maximum size is 10MB' });
204
+ }
205
+ }
206
+
207
+ console.error('Unhandled error:', error);
208
+ res.status(500).json({ error: 'Internal server error' });
209
+ });
210
+
211
+ // Start server
212
+ async function startServer() {
213
+ try {
214
+ // Initialize TensorFlow.js
215
+ const tfInitialized = await initializeTensorFlow();
216
+ if (!tfInitialized) {
217
+ console.error('Failed to initialize TensorFlow.js. Exiting...');
218
+ process.exit(1);
219
+ }
220
+
221
+ // Start the server
222
+ app.listen(PORT, '0.0.0.0', () => {
223
+ console.log(`πŸš€ Upscaler API server running on port ${PORT}`);
224
+ console.log(`πŸ“Š TensorFlow.js backend: ${tf.getBackend()}`);
225
+ console.log(`πŸ”— Health check: http://localhost:${PORT}/`);
226
+ });
227
+ } catch (error) {
228
+ console.error('Failed to start server:', error);
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ // Handle graceful shutdown
234
+ process.on('SIGTERM', () => {
235
+ console.log('Received SIGTERM, shutting down gracefully...');
236
+ if (upscalerInstance) {
237
+ try {
238
+ upscalerInstance.dispose();
239
+ } catch (error) {
240
+ console.warn('Error disposing upscaler:', error);
241
+ }
242
+ }
243
+ process.exit(0);
244
+ });
245
+
246
+ // Start the server
247
+ startServer();