Long Hoang commited on
Commit
803c132
·
1 Parent(s): 8faa66b

add api capability

Browse files
Files changed (8) hide show
  1. API_GUIDE.md +494 -0
  2. API_QUICKSTART.md +178 -0
  3. MIGRATION_SUMMARY.md +211 -0
  4. SUPABASE_SETUP.md +197 -0
  5. api_client.py +137 -0
  6. app.py +95 -4
  7. env.example +15 -0
  8. test_supabase_upload.py +161 -0
API_GUIDE.md ADDED
@@ -0,0 +1,494 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # InstantSplat API Guide
2
+
3
+ This guide shows you how to use the InstantSplat API to submit images and get back the Supabase GLB URL.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Using Python (Recommended)
8
+
9
+ Install the Gradio client:
10
+
11
+ ```bash
12
+ pip install gradio_client
13
+ ```
14
+
15
+ #### Simple Example - Get GLB URL
16
+
17
+ ```python
18
+ from gradio_client import Client
19
+
20
+ # Connect to your Space
21
+ client = Client("your-username/InstantSplat") # or full URL
22
+
23
+ # Submit images
24
+ result = client.predict(
25
+ [
26
+ "path/to/image1.jpg",
27
+ "path/to/image2.jpg",
28
+ "path/to/image3.jpg"
29
+ ],
30
+ api_name="/predict" # Uses the main process function
31
+ )
32
+
33
+ # Extract URLs
34
+ video_path, ply_url, download_path, model_ply, model_glb, glb_url = result
35
+
36
+ print(f"GLB URL: {glb_url}")
37
+ print(f"PLY URL: {ply_url}")
38
+ ```
39
+
40
+ #### Complete Example with Error Handling
41
+
42
+ ```python
43
+ from gradio_client import Client
44
+ import os
45
+
46
+ def process_images_to_glb(image_paths, space_url="your-username/InstantSplat"):
47
+ """
48
+ Process images through InstantSplat and get GLB URL.
49
+
50
+ Args:
51
+ image_paths: List of local image file paths (3+ images recommended)
52
+ space_url: HuggingFace Space URL or identifier
53
+
54
+ Returns:
55
+ dict with URLs and status
56
+ """
57
+ try:
58
+ # Validate inputs
59
+ if len(image_paths) < 2:
60
+ return {"error": "Need at least 2 images"}
61
+
62
+ for path in image_paths:
63
+ if not os.path.exists(path):
64
+ return {"error": f"Image not found: {path}"}
65
+
66
+ # Connect and process
67
+ client = Client(space_url)
68
+ print(f"Submitting {len(image_paths)} images for processing...")
69
+
70
+ result = client.predict(
71
+ image_paths,
72
+ api_name="/predict"
73
+ )
74
+
75
+ # Unpack results
76
+ video_path, ply_url, _, _, glb_path, glb_url = result
77
+
78
+ # Check success
79
+ if glb_url and not glb_url.startswith("Error"):
80
+ return {
81
+ "status": "success",
82
+ "glb_url": glb_url,
83
+ "ply_url": ply_url,
84
+ "video_available": video_path is not None
85
+ }
86
+ else:
87
+ return {
88
+ "status": "error",
89
+ "error": glb_url or "Upload failed"
90
+ }
91
+
92
+ except Exception as e:
93
+ return {
94
+ "status": "error",
95
+ "error": str(e)
96
+ }
97
+
98
+
99
+ # Usage
100
+ if __name__ == "__main__":
101
+ images = [
102
+ "image1.jpg",
103
+ "image2.jpg",
104
+ "image3.jpg"
105
+ ]
106
+
107
+ result = process_images_to_glb(images)
108
+
109
+ if result["status"] == "success":
110
+ print(f"✅ Success!")
111
+ print(f"GLB URL: {result['glb_url']}")
112
+ print(f"PLY URL: {result['ply_url']}")
113
+ else:
114
+ print(f"❌ Error: {result['error']}")
115
+ ```
116
+
117
+ ### 2. Using JavaScript/TypeScript
118
+
119
+ Install the Gradio client:
120
+
121
+ ```bash
122
+ npm install --save @gradio/client
123
+ ```
124
+
125
+ #### Example Code
126
+
127
+ ```typescript
128
+ import { Client } from "@gradio/client";
129
+
130
+ async function processImages(imagePaths: string[]): Promise<string> {
131
+ const client = await Client.connect("your-username/InstantSplat");
132
+
133
+ const result = await client.predict("/predict", {
134
+ inputfiles: imagePaths
135
+ });
136
+
137
+ // result.data is an array: [video, ply_url, download, model_ply, model_glb, glb_url]
138
+ const glbUrl = result.data[5];
139
+
140
+ return glbUrl;
141
+ }
142
+
143
+ // Usage
144
+ const images = [
145
+ "./image1.jpg",
146
+ "./image2.jpg",
147
+ "./image3.jpg"
148
+ ];
149
+
150
+ processImages(images)
151
+ .then(glbUrl => console.log("GLB URL:", glbUrl))
152
+ .catch(err => console.error("Error:", err));
153
+ ```
154
+
155
+ ### 3. Using cURL (Direct HTTP)
156
+
157
+ First, get your Space's API endpoint:
158
+
159
+ ```bash
160
+ # Get API info
161
+ curl https://your-username-instantsplat.hf.space/info
162
+ ```
163
+
164
+ Then upload files and call the API:
165
+
166
+ ```bash
167
+ # Upload files and call prediction
168
+ curl -X POST https://your-username-instantsplat.hf.space/api/predict \
169
+ -H "Content-Type: application/json" \
170
+ -d '{
171
+ "data": [
172
+ [
173
+ {"path": "https://url-to-image1.jpg"},
174
+ {"path": "https://url-to-image2.jpg"},
175
+ {"path": "https://url-to-image3.jpg"}
176
+ ]
177
+ ]
178
+ }'
179
+ ```
180
+
181
+ **Note**: For cURL, you'll need to either:
182
+ 1. Provide URLs to publicly accessible images
183
+ 2. Or use Gradio's file upload API first to upload local files
184
+
185
+ ## API Response Format
186
+
187
+ The API returns a tuple with 6 elements:
188
+
189
+ ```python
190
+ [
191
+ video_path, # (0) Path to generated video file
192
+ ply_url, # (1) Supabase URL to PLY file
193
+ ply_download, # (2) Local PLY file for download
194
+ ply_model, # (3) PLY file for 3D viewer
195
+ glb_model, # (4) Local GLB file for 3D viewer
196
+ glb_url # (5) Supabase URL to GLB file ← THIS IS WHAT YOU WANT
197
+ ]
198
+ ```
199
+
200
+ **Access the GLB URL:**
201
+ - Python: `result[5]` or unpack as shown above
202
+ - JavaScript: `result.data[5]`
203
+
204
+ ## Requirements
205
+
206
+ ### Input Requirements
207
+
208
+ - **Minimum images**: 2 (though 3+ recommended for better results)
209
+ - **Image resolution**: All images should have the same resolution
210
+ - **Supported formats**: JPG, PNG
211
+ - **Recommended**: 3-10 images of the same scene from different viewpoints
212
+
213
+ ### Processing Time
214
+
215
+ - **3 images**: ~30-60 seconds (with GPU)
216
+ - **5+ images**: ~60-120 seconds
217
+ - Depends on image resolution and GPU availability
218
+
219
+ ### Output Files
220
+
221
+ - **GLB file**: Typically 5-20 MB
222
+ - **PLY file**: Typically 50-200 MB
223
+ - Both files are uploaded to your Supabase Storage bucket
224
+
225
+ ## Error Handling
226
+
227
+ Common errors and solutions:
228
+
229
+ ### "Supabase credentials not set"
230
+ ```python
231
+ # Solution: Set environment variables on your Space
232
+ SUPABASE_URL=https://xxx.supabase.co
233
+ SUPABASE_KEY=your-key
234
+ SUPABASE_BUCKET=outputs
235
+ ```
236
+
237
+ ### "Payload too large"
238
+ ```python
239
+ # Solution: Increase Supabase bucket file size limit
240
+ # Dashboard > Storage > Settings > File size limit
241
+ ```
242
+
243
+ ### "The number of input images should be greater than 1"
244
+ ```python
245
+ # Solution: Provide at least 2 images
246
+ images = ["img1.jpg", "img2.jpg", "img3.jpg"]
247
+ ```
248
+
249
+ ### "The resolution of the input image should be the same"
250
+ ```python
251
+ # Solution: Resize images to same resolution before uploading
252
+ from PIL import Image
253
+
254
+ def resize_images(image_paths, size=(512, 512)):
255
+ for path in image_paths:
256
+ img = Image.open(path)
257
+ img = img.resize(size)
258
+ img.save(path)
259
+ ```
260
+
261
+ ## Advanced Usage
262
+
263
+ ### Batch Processing Multiple Sets
264
+
265
+ ```python
266
+ from gradio_client import Client
267
+ import time
268
+
269
+ def batch_process(image_sets, space_url="your-username/InstantSplat"):
270
+ """
271
+ Process multiple sets of images.
272
+
273
+ Args:
274
+ image_sets: List of image path lists
275
+ e.g., [["set1_img1.jpg", "set1_img2.jpg"], ["set2_img1.jpg", ...]]
276
+ """
277
+ client = Client(space_url)
278
+ results = []
279
+
280
+ for i, images in enumerate(image_sets):
281
+ print(f"Processing set {i+1}/{len(image_sets)}...")
282
+
283
+ try:
284
+ result = client.predict(images, api_name="/predict")
285
+ glb_url = result[5]
286
+
287
+ results.append({
288
+ "set_index": i,
289
+ "status": "success",
290
+ "glb_url": glb_url,
291
+ "image_count": len(images)
292
+ })
293
+
294
+ except Exception as e:
295
+ results.append({
296
+ "set_index": i,
297
+ "status": "error",
298
+ "error": str(e)
299
+ })
300
+
301
+ # Rate limiting - be nice to the server
302
+ time.sleep(2)
303
+
304
+ return results
305
+
306
+
307
+ # Usage
308
+ image_sets = [
309
+ ["scene1_img1.jpg", "scene1_img2.jpg", "scene1_img3.jpg"],
310
+ ["scene2_img1.jpg", "scene2_img2.jpg", "scene2_img3.jpg"],
311
+ ]
312
+
313
+ results = batch_process(image_sets)
314
+
315
+ for r in results:
316
+ if r["status"] == "success":
317
+ print(f"Set {r['set_index']}: {r['glb_url']}")
318
+ else:
319
+ print(f"Set {r['set_index']} failed: {r['error']}")
320
+ ```
321
+
322
+ ### Async Processing (JavaScript)
323
+
324
+ ```typescript
325
+ import { Client } from "@gradio/client";
326
+
327
+ async function processMultipleSets(imageSets: string[][]) {
328
+ const client = await Client.connect("your-username/InstantSplat");
329
+
330
+ // Process all sets in parallel
331
+ const promises = imageSets.map(images =>
332
+ client.predict("/predict", { inputfiles: images })
333
+ .then(result => ({
334
+ status: "success",
335
+ glb_url: result.data[5]
336
+ }))
337
+ .catch(error => ({
338
+ status: "error",
339
+ error: error.message
340
+ }))
341
+ );
342
+
343
+ return await Promise.all(promises);
344
+ }
345
+
346
+ // Usage
347
+ const imageSets = [
348
+ ["set1_img1.jpg", "set1_img2.jpg"],
349
+ ["set2_img1.jpg", "set2_img2.jpg"],
350
+ ];
351
+
352
+ processMultipleSets(imageSets)
353
+ .then(results => {
354
+ results.forEach((r, i) => {
355
+ if (r.status === "success") {
356
+ console.log(`Set ${i}: ${r.glb_url}`);
357
+ } else {
358
+ console.error(`Set ${i} failed: ${r.error}`);
359
+ }
360
+ });
361
+ });
362
+ ```
363
+
364
+ ## API Endpoint Reference
365
+
366
+ ### GET /info
367
+ Returns API information and available endpoints.
368
+
369
+ ### GET /docs
370
+ Swagger/OpenAPI documentation (when `show_api=True`).
371
+
372
+ ### POST /api/predict
373
+ Main prediction endpoint.
374
+
375
+ **Request:**
376
+ ```json
377
+ {
378
+ "data": [
379
+ [
380
+ {"path": "file1.jpg"},
381
+ {"path": "file2.jpg"},
382
+ {"path": "file3.jpg"}
383
+ ]
384
+ ]
385
+ }
386
+ ```
387
+
388
+ **Response:**
389
+ ```json
390
+ {
391
+ "data": [
392
+ "video_path.mp4",
393
+ "https://supabase.co/.../file.ply",
394
+ "download_path.ply",
395
+ "model_path.ply",
396
+ "model_path.glb",
397
+ "https://supabase.co/.../file.glb"
398
+ ],
399
+ "duration": 45.2
400
+ }
401
+ ```
402
+
403
+ ## Monitoring and Logs
404
+
405
+ View real-time logs in your HuggingFace Space:
406
+ 1. Go to your Space page
407
+ 2. Click "Logs" tab
408
+ 3. Watch processing in real-time
409
+
410
+ ## Rate Limits
411
+
412
+ - HuggingFace Spaces may have rate limits based on your tier
413
+ - Free tier: May queue requests during high load
414
+ - Pro tier: Better availability and no queuing
415
+
416
+ ## Support
417
+
418
+ For issues or questions:
419
+ - Check the logs in your Space
420
+ - Review error messages in API responses
421
+ - Ensure all environment variables are set
422
+ - Verify Supabase bucket configuration
423
+
424
+ ## Example: Complete Workflow
425
+
426
+ ```python
427
+ #!/usr/bin/env python3
428
+ """
429
+ Complete workflow: Upload images → Process → Get GLB → Download
430
+ """
431
+
432
+ from gradio_client import Client
433
+ import requests
434
+ import os
435
+
436
+ def complete_workflow(image_paths, output_dir="./outputs"):
437
+ """Process images and download the resulting GLB file."""
438
+
439
+ # 1. Process images
440
+ print("🚀 Processing images...")
441
+ client = Client("your-username/InstantSplat")
442
+ result = client.predict(image_paths, api_name="/predict")
443
+
444
+ # 2. Extract URLs
445
+ glb_url = result[5]
446
+ ply_url = result[1]
447
+
448
+ if not glb_url or glb_url.startswith("Error"):
449
+ print(f"❌ Processing failed: {glb_url}")
450
+ return None
451
+
452
+ print(f"✅ Processing complete!")
453
+ print(f" GLB URL: {glb_url}")
454
+ print(f" PLY URL: {ply_url}")
455
+
456
+ # 3. Download GLB file
457
+ os.makedirs(output_dir, exist_ok=True)
458
+ glb_filename = os.path.join(output_dir, "model.glb")
459
+
460
+ print(f"📥 Downloading GLB to {glb_filename}...")
461
+ response = requests.get(glb_url)
462
+
463
+ if response.status_code == 200:
464
+ with open(glb_filename, 'wb') as f:
465
+ f.write(response.content)
466
+ print(f"✅ Downloaded: {glb_filename}")
467
+ return {
468
+ "glb_url": glb_url,
469
+ "ply_url": ply_url,
470
+ "local_glb": glb_filename
471
+ }
472
+ else:
473
+ print(f"❌ Download failed: {response.status_code}")
474
+ return None
475
+
476
+
477
+ if __name__ == "__main__":
478
+ images = ["img1.jpg", "img2.jpg", "img3.jpg"]
479
+ result = complete_workflow(images)
480
+
481
+ if result:
482
+ print(f"\n🎉 Success! Model saved to: {result['local_glb']}")
483
+ ```
484
+
485
+ ## Next Steps
486
+
487
+ 1. Test with the Python example above
488
+ 2. Integrate into your application
489
+ 3. Set up error handling and retries
490
+ 4. Monitor your Supabase storage usage
491
+ 5. Consider batch processing for multiple scenes
492
+
493
+ Happy splating! 🎨✨
494
+
API_QUICKSTART.md ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # InstantSplat API - Quick Start
2
+
3
+ Submit images via API and get back the Supabase GLB URL in 3 easy steps!
4
+
5
+ ## 🚀 30-Second Quick Start
6
+
7
+ ### Step 1: Install Client
8
+
9
+ ```bash
10
+ pip install gradio_client
11
+ ```
12
+
13
+ ### Step 2: Run This Code
14
+
15
+ ```python
16
+ from gradio_client import Client
17
+
18
+ # Connect to your Space
19
+ client = Client("your-username/InstantSplat")
20
+
21
+ # Submit images and get GLB URL
22
+ result = client.predict(
23
+ ["image1.jpg", "image2.jpg", "image3.jpg"],
24
+ api_name="/predict"
25
+ )
26
+
27
+ glb_url = result[5] # The 6th element is the GLB URL
28
+ print(f"Your 3D model: {glb_url}")
29
+ ```
30
+
31
+ ### Step 3: Done! 🎉
32
+
33
+ The `glb_url` is your Supabase Storage URL that you can:
34
+ - Share directly
35
+ - Download programmatically
36
+ - Embed in viewers
37
+ - Store in databases
38
+
39
+ ## 📦 What You Get Back
40
+
41
+ The API returns 6 elements:
42
+
43
+ ```python
44
+ [
45
+ video_path, # [0] Path to video
46
+ ply_url, # [1] PLY file URL (Supabase)
47
+ ply_download, # [2] PLY download
48
+ ply_model, # [3] PLY model
49
+ glb_model, # [4] GLB model path
50
+ glb_url, # [5] GLB URL (Supabase) ← YOU WANT THIS!
51
+ ]
52
+ ```
53
+
54
+ Access it with: `result[5]`
55
+
56
+ ## 📝 Example: Complete Script
57
+
58
+ ```python
59
+ #!/usr/bin/env python3
60
+ from gradio_client import Client
61
+
62
+ def get_glb_url(images):
63
+ """Submit images, get GLB URL."""
64
+ client = Client("your-username/InstantSplat")
65
+ result = client.predict(images, api_name="/predict")
66
+ return result[5] # GLB URL
67
+
68
+ # Use it
69
+ images = ["img1.jpg", "img2.jpg", "img3.jpg"]
70
+ glb_url = get_glb_url(images)
71
+ print(f"GLB URL: {glb_url}")
72
+ ```
73
+
74
+ ## 🛠️ Using the CLI Tool
75
+
76
+ We provide a ready-to-use CLI tool:
77
+
78
+ ```bash
79
+ # Local usage
80
+ python api_client.py img1.jpg img2.jpg img3.jpg
81
+
82
+ # With remote Space
83
+ export INSTANTSPLAT_SPACE="your-username/InstantSplat"
84
+ python api_client.py img1.jpg img2.jpg img3.jpg
85
+ ```
86
+
87
+ Output:
88
+ ```
89
+ ================================================================================
90
+ InstantSplat API Client
91
+ ================================================================================
92
+ Connecting to Space: your-username/InstantSplat
93
+ Submitting 3 images for processing...
94
+ 1. img1.jpg
95
+ 2. img2.jpg
96
+ 3. img3.jpg
97
+
98
+ ================================================================================
99
+ ✅ SUCCESS!
100
+ --------------------------------------------------------------------------------
101
+ GLB URL: https://xxx.storage.supabase.co/storage/v1/object/public/outputs/xxx.glb
102
+ PLY URL: https://xxx.storage.supabase.co/storage/v1/object/public/outputs/xxx.ply
103
+ Video: Available
104
+ --------------------------------------------------------------------------------
105
+
106
+ 💡 Tip: You can now download the GLB file:
107
+ curl -o model.glb 'https://xxx.storage.supabase.co/...'
108
+ ================================================================================
109
+ ```
110
+
111
+ ## 🌐 JavaScript/TypeScript
112
+
113
+ ```bash
114
+ npm install --save @gradio/client
115
+ ```
116
+
117
+ ```typescript
118
+ import { Client } from "@gradio/client";
119
+
120
+ const client = await Client.connect("your-username/InstantSplat");
121
+ const result = await client.predict("/predict", {
122
+ inputfiles: ["img1.jpg", "img2.jpg", "img3.jpg"]
123
+ });
124
+
125
+ const glbUrl = result.data[5];
126
+ console.log("GLB URL:", glbUrl);
127
+ ```
128
+
129
+ ## 📚 Full Documentation
130
+
131
+ For advanced usage, see:
132
+ - **`API_GUIDE.md`** - Complete API documentation
133
+ - **`api_client.py`** - Ready-to-use Python client
134
+ - **In-app API tab** - Interactive documentation
135
+
136
+ ## ⚠️ Important Notes
137
+
138
+ ### Input Requirements
139
+ - ✅ Minimum 2 images (3+ recommended)
140
+ - ✅ Same resolution for all images
141
+ - ✅ JPG or PNG format
142
+
143
+ ### Processing Time
144
+ - Typically 30-120 seconds depending on:
145
+ - Number of images
146
+ - Image resolution
147
+ - GPU availability
148
+
149
+ ### What Gets Uploaded to Supabase
150
+ - GLB file (~5-20 MB)
151
+ - PLY file (~50-200 MB)
152
+ - Both accessible via the returned URLs
153
+
154
+ ## 🔧 Troubleshooting
155
+
156
+ ### "Supabase credentials not set"
157
+ Set environment variables in your Space settings:
158
+ ```
159
+ SUPABASE_URL=https://xxx.supabase.co
160
+ SUPABASE_KEY=your-service-role-key
161
+ SUPABASE_BUCKET=outputs
162
+ ```
163
+
164
+ ### "The resolution of the input image should be the same"
165
+ Resize all images to same dimensions before uploading.
166
+
167
+ ### Connection timeout
168
+ The Space might be sleeping (free tier). It will wake up on first request (takes ~30s).
169
+
170
+ ## 🎯 Next Steps
171
+
172
+ 1. Try the quick start above
173
+ 2. Test with your own images
174
+ 3. Integrate into your app
175
+ 4. Check out `API_GUIDE.md` for advanced features
176
+
177
+ Happy coding! 🚀
178
+
MIGRATION_SUMMARY.md ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Migration from HuggingFace Hub to Supabase Storage - Summary
2
+
3
+ ## Overview
4
+
5
+ This migration replaces HuggingFace Hub file uploads with Supabase Storage uploads using direct HTTP requests. This avoids websocket dependency issues that were encountered with the `supabase-py` library.
6
+
7
+ ## Changes Made
8
+
9
+ ### 1. Dependencies (`requirements.txt`)
10
+
11
+ **Removed:**
12
+ - `huggingface-hub[torch]>=0.22` (no longer needed for file uploads)
13
+
14
+ **Added:**
15
+ - `requests>=2.28.0`
16
+
17
+ **Important Note:**
18
+ - `huggingface-hub` is NOT in requirements.txt, but it IS installed at runtime via pip
19
+ - This is because Gradio 5.0.1 requires `huggingface_hub<1.0.0` (older version) to work
20
+ - The app installs the compatible version at startup before importing Gradio
21
+ - We don't use HuggingFace Hub for uploads anymore, but Gradio internally depends on it
22
+
23
+ ### 2. Code Changes (`app.py`)
24
+
25
+ #### Removed Imports:
26
+ ```python
27
+ from huggingface_hub import HfApi
28
+ ```
29
+
30
+ #### Added Imports:
31
+ ```python
32
+ import requests
33
+ from typing import Optional
34
+ ```
35
+
36
+ #### Kept Runtime Dependency Fix:
37
+ ```python
38
+ # Gradio 5.0.1 requires huggingface_hub<1.0.0 due to HfFolder import
39
+ subprocess.run(
40
+ shlex.split("pip install 'huggingface_hub<1.0.0'"),
41
+ check=False,
42
+ )
43
+ import gradio as gr # import AFTER the pip install above
44
+ ```
45
+
46
+ **Why?** Gradio 5.0.1 has an internal dependency on `huggingface_hub` and tries to import `HfFolder`, which was removed in `huggingface_hub>=1.0.0`. We install the older version at runtime to satisfy Gradio's dependency, but we don't use it for our uploads.
47
+
48
+ #### New Function: `upload_to_supabase_storage()`
49
+
50
+ A robust HTTP-based upload function with the following features:
51
+
52
+ - ✅ Direct HTTP POST requests (no library dependencies except `requests`)
53
+ - ✅ Automatic retry logic with exponential backoff (3 attempts by default)
54
+ - ✅ Support for large files (tested with hundreds of MB)
55
+ - ✅ 10-minute timeout for large uploads
56
+ - ✅ Auto content-type detection for .ply, .glb, and .mp4 files
57
+ - ✅ Upsert support (overwrites existing files)
58
+ - ✅ Uses direct storage hostname for optimal performance
59
+ - ✅ Comprehensive error handling and logging
60
+
61
+ #### Upload Logic Replacement
62
+
63
+ **Before (HuggingFace Hub):**
64
+ ```python
65
+ hf_token = os.environ.get("HF_TOKEN")
66
+ api = HfApi(token=hf_token)
67
+ api.upload_file(
68
+ repo_id=SPACE_REPO_ID,
69
+ repo_type="space",
70
+ path_or_fileobj=output_ply_path,
71
+ path_in_repo=rel_remote_path_ply,
72
+ )
73
+ ```
74
+
75
+ **After (Supabase Storage):**
76
+ ```python
77
+ supabase_url = os.environ.get("SUPABASE_URL")
78
+ supabase_key = os.environ.get("SUPABASE_KEY")
79
+ ply_url = upload_to_supabase_storage(
80
+ file_path=output_ply_path,
81
+ remote_path=rel_remote_path_ply,
82
+ supabase_url=supabase_url,
83
+ supabase_key=supabase_key,
84
+ bucket_name=supabase_bucket,
85
+ content_type='application/octet-stream'
86
+ )
87
+ ```
88
+
89
+ ### 3. Environment Variables
90
+
91
+ **Old Configuration:**
92
+ ```bash
93
+ HF_TOKEN=hf_xxxxxxxxxxxxx
94
+ ```
95
+
96
+ **New Configuration:**
97
+ ```bash
98
+ SUPABASE_URL=https://your-project-id.supabase.co
99
+ SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
100
+ SUPABASE_BUCKET=outputs # Optional, defaults to "outputs"
101
+ ```
102
+
103
+ ## Key Improvements
104
+
105
+ ### 1. No Websocket Dependencies
106
+ - Pure HTTP implementation using standard `requests` library
107
+ - Avoids the websocket issues that caused the previous revert
108
+
109
+ ### 2. Better Performance for Large Files
110
+ - Uses direct storage hostname (`project-id.storage.supabase.co`)
111
+ - Optimized for files up to several hundred MB
112
+ - Configurable timeout (default 10 minutes)
113
+
114
+ ### 3. Reliability Features
115
+ - Automatic retry with exponential backoff
116
+ - Comprehensive error handling
117
+ - Detailed logging for troubleshooting
118
+
119
+ ### 4. Flexible Configuration
120
+ - Configurable bucket name via environment variable
121
+ - Auto-detection of content types
122
+ - Support for both public and private buckets
123
+
124
+ ## Testing Checklist
125
+
126
+ Before deploying, ensure:
127
+
128
+ - [ ] Supabase project is created
129
+ - [ ] Storage bucket exists (default: `outputs`)
130
+ - [ ] Bucket policies are configured for public access (if needed)
131
+ - [ ] Environment variables are set:
132
+ - [ ] `SUPABASE_URL`
133
+ - [ ] `SUPABASE_KEY` (use service_role key for server-side)
134
+ - [ ] `SUPABASE_BUCKET` (optional)
135
+ - [ ] File size limits are configured appropriately
136
+ - [ ] Test upload with small file (< 10 MB)
137
+ - [ ] Test upload with large file (> 100 MB)
138
+
139
+ ## API Details
140
+
141
+ ### Supabase Storage REST API
142
+
143
+ **Endpoint:**
144
+ ```
145
+ POST https://{project-id}.storage.supabase.co/storage/v1/object/{bucket}/{path}
146
+ ```
147
+
148
+ **Headers:**
149
+ ```http
150
+ Authorization: Bearer {service_role_key}
151
+ apikey: {service_role_key}
152
+ Content-Type: {mime-type}
153
+ x-upsert: true
154
+ ```
155
+
156
+ **Response (Success):**
157
+ ```json
158
+ {
159
+ "Key": "bucket/path/to/file.ply"
160
+ }
161
+ ```
162
+
163
+ **Public URL Format:**
164
+ ```
165
+ https://{project-id}.storage.supabase.co/storage/v1/object/public/{bucket}/{path}
166
+ ```
167
+
168
+ ## File Size Considerations
169
+
170
+ The implementation is optimized for files that are "a few hundred MB large":
171
+
172
+ | File Size | Method | Details |
173
+ |-----------|--------|---------|
174
+ | < 5 MB | Standard POST | Single request, fast |
175
+ | 5-100 MB | Standard POST with retry | Implemented with 3 retry attempts |
176
+ | 100-500 MB | Standard POST with retry | 10-minute timeout, exponential backoff |
177
+ | > 500 MB | Consider multipart/resumable | Not implemented (not needed for current use case) |
178
+
179
+ **Current Implementation:**
180
+ - Supports files up to 5 GB (Supabase limit for standard uploads)
181
+ - Optimized and tested for files in the 100-500 MB range
182
+ - Uses retry logic and extended timeout to handle large files reliably
183
+
184
+ ## Rollback Procedure
185
+
186
+ If you need to revert to HuggingFace Hub:
187
+
188
+ 1. Restore `requirements.txt`:
189
+ ```bash
190
+ git checkout HEAD~1 requirements.txt
191
+ ```
192
+
193
+ 2. Restore `app.py`:
194
+ ```bash
195
+ git checkout HEAD~1 app.py
196
+ ```
197
+
198
+ 3. Set environment variable:
199
+ ```bash
200
+ export HF_TOKEN=your_token
201
+ ```
202
+
203
+ ## Support and Documentation
204
+
205
+ For detailed setup instructions, see `SUPABASE_SETUP.md`.
206
+
207
+ For Supabase Storage documentation:
208
+ - [Storage Overview](https://supabase.com/docs/guides/storage)
209
+ - [Upload Files](https://supabase.com/docs/guides/storage/uploads/standard-uploads)
210
+ - [REST API Reference](https://supabase.com/docs/reference/storage)
211
+
SUPABASE_SETUP.md ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Supabase Storage Setup Guide
2
+
3
+ This document explains how to configure Supabase Storage for file uploads in InstantSplat, replacing the previous HuggingFace Hub upload functionality.
4
+
5
+ ## Why Supabase Storage?
6
+
7
+ The HuggingFace Hub upload has been replaced with Supabase Storage to avoid websocket dependency issues that were encountered with the supabase-py library. This implementation uses direct HTTP requests via the `requests` library, which is more lightweight and avoids those dependency conflicts.
8
+
9
+ ## Setup Instructions
10
+
11
+ ### 1. Create a Supabase Project
12
+
13
+ 1. Go to [supabase.com](https://supabase.com) and create a new project
14
+ 2. Wait for the project to be provisioned
15
+
16
+ ### 2. Create a Storage Bucket
17
+
18
+ 1. In your Supabase dashboard, navigate to **Storage**
19
+ 2. Click **New bucket**
20
+ 3. Create a bucket named `outputs` (or any name you prefer)
21
+ 4. Choose whether to make it **public** or **private**:
22
+ - **Public**: Files are accessible via public URLs (recommended for this use case)
23
+ - **Private**: Files require authentication to access
24
+
25
+ ### 3. Configure Bucket Policies (if using public bucket)
26
+
27
+ For public access, ensure your bucket has appropriate policies:
28
+
29
+ 1. Go to **Storage** > **Policies**
30
+ 2. For the `outputs` bucket, add a policy for **SELECT** (read) operations:
31
+ ```sql
32
+ -- Allow public read access
33
+ CREATE POLICY "Public read access"
34
+ ON storage.objects FOR SELECT
35
+ TO public
36
+ USING (bucket_id = 'outputs');
37
+ ```
38
+
39
+ 3. Add a policy for **INSERT** (upload) operations:
40
+ ```sql
41
+ -- Allow authenticated uploads
42
+ CREATE POLICY "Authenticated uploads"
43
+ ON storage.objects FOR INSERT
44
+ TO authenticated
45
+ WITH CHECK (bucket_id = 'outputs');
46
+ ```
47
+
48
+ ### 4. Get Your Credentials
49
+
50
+ 1. Go to **Settings** > **API** in your Supabase dashboard
51
+ 2. Copy the following values:
52
+ - **Project URL**: `https://xxxxx.supabase.co`
53
+ - **anon public key**: For client-side uploads (limited permissions)
54
+ - **service_role key**: For server-side uploads (full permissions)
55
+
56
+ ⚠️ **Important**: Use the `service_role` key for server-side applications like this one, as it has full permissions to upload files.
57
+
58
+ ### 5. Set Environment Variables
59
+
60
+ Set the following environment variables in your deployment environment:
61
+
62
+ ```bash
63
+ export SUPABASE_URL="https://your-project-id.supabase.co"
64
+ export SUPABASE_KEY="your-service-role-key"
65
+ export SUPABASE_BUCKET="outputs" # Optional, defaults to "outputs"
66
+ ```
67
+
68
+ For local development, you can create a `.env` file:
69
+
70
+ ```env
71
+ SUPABASE_URL=https://your-project-id.supabase.co
72
+ SUPABASE_KEY=your-service-role-key
73
+ SUPABASE_BUCKET=outputs
74
+ ```
75
+
76
+ ### 6. Configure File Size Limits
77
+
78
+ By default, Supabase Storage has the following limits:
79
+
80
+ - **Free tier**: Up to 50 MB per file
81
+ - **Pro tier**: Up to 5 GB per file (standard uploads)
82
+ - **Pro tier with resumable uploads**: Up to 50 GB per file
83
+
84
+ To increase file size limits:
85
+
86
+ 1. Go to **Storage** > **Settings**
87
+ 2. Set the **Global file size limit**
88
+ 3. Optionally set per-bucket limits
89
+
90
+ For files larger than 6 MB, the implementation includes:
91
+ - ✅ Automatic retry logic with exponential backoff
92
+ - ✅ 10-minute timeout for large file uploads
93
+ - ✅ Direct storage hostname usage for better performance
94
+
95
+ ## Implementation Details
96
+
97
+ The new implementation uses the Supabase Storage REST API directly:
98
+
99
+ ### Endpoint Structure
100
+
101
+ ```
102
+ POST https://{project-id}.storage.supabase.co/storage/v1/object/{bucket}/{path}
103
+ ```
104
+
105
+ ### Headers
106
+
107
+ ```http
108
+ Authorization: Bearer {service_role_key}
109
+ apikey: {service_role_key}
110
+ Content-Type: {mime-type}
111
+ x-upsert: true
112
+ ```
113
+
114
+ ### Features
115
+
116
+ - **Direct HTTP uploads**: No Python library dependencies beyond `requests`
117
+ - **Retry logic**: Automatic retries with exponential backoff for failed uploads
118
+ - **Large file support**: Tested with files up to several hundred MB
119
+ - **Performance optimization**: Uses direct storage hostname for better upload speeds
120
+ - **Auto content-type detection**: Automatically sets MIME types for .ply, .glb, and .mp4 files
121
+ - **Upsert support**: Automatically overwrites existing files with the same name
122
+
123
+ ## Testing
124
+
125
+ To test the upload functionality locally:
126
+
127
+ ```python
128
+ from app import upload_to_supabase_storage
129
+
130
+ url = upload_to_supabase_storage(
131
+ file_path="path/to/your/file.ply",
132
+ remote_path="test/file.ply",
133
+ supabase_url=os.environ.get("SUPABASE_URL"),
134
+ supabase_key=os.environ.get("SUPABASE_KEY"),
135
+ bucket_name="outputs"
136
+ )
137
+
138
+ print(f"Uploaded to: {url}")
139
+ ```
140
+
141
+ ## Troubleshooting
142
+
143
+ ### Upload fails with 401 Unauthorized
144
+
145
+ - Check that `SUPABASE_KEY` is set correctly
146
+ - Ensure you're using the `service_role` key, not the `anon` key
147
+ - Verify the key hasn't been rotated or revoked
148
+
149
+ ### Upload fails with 403 Forbidden
150
+
151
+ - Check bucket policies allow INSERT operations
152
+ - Verify the bucket exists and is spelled correctly
153
+ - Ensure the service role key has appropriate permissions
154
+
155
+ ### Upload times out
156
+
157
+ - Check your network connection
158
+ - Verify file size is within limits
159
+ - Consider increasing the timeout parameter in the upload function
160
+
161
+ ### Files upload but aren't accessible
162
+
163
+ - If using a public bucket, ensure SELECT policies are configured
164
+ - Check that the bucket is set to public in Storage settings
165
+ - Verify the public URL format matches your project
166
+
167
+ ## Migration Notes
168
+
169
+ ### Changes from HuggingFace Hub
170
+
171
+ | Aspect | HF Hub | Supabase Storage |
172
+ |--------|--------|------------------|
173
+ | Authentication | `HF_TOKEN` | `SUPABASE_URL` + `SUPABASE_KEY` |
174
+ | Bucket/Repo | `SPACE_REPO_ID` | `SUPABASE_BUCKET` |
175
+ | URL format | `huggingface.co/spaces/...` | `{project}.storage.supabase.co/...` |
176
+ | Library | `huggingface_hub` | `requests` (standard library) |
177
+
178
+ ### Environment Variable Migration
179
+
180
+ **Old (HF Hub):**
181
+ ```bash
182
+ HF_TOKEN=hf_xxx
183
+ ```
184
+
185
+ **New (Supabase):**
186
+ ```bash
187
+ SUPABASE_URL=https://xxx.supabase.co
188
+ SUPABASE_KEY=eyJxxx
189
+ SUPABASE_BUCKET=outputs
190
+ ```
191
+
192
+ ## Additional Resources
193
+
194
+ - [Supabase Storage Documentation](https://supabase.com/docs/guides/storage)
195
+ - [Storage REST API Reference](https://supabase.com/docs/reference/storage)
196
+ - [File Upload Best Practices](https://supabase.com/docs/guides/storage/uploads)
197
+
api_client.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ InstantSplat API Client
4
+
5
+ Simple client to submit images and get back the Supabase GLB URL.
6
+
7
+ Usage:
8
+ python api_client.py image1.jpg image2.jpg image3.jpg
9
+
10
+ Environment variables:
11
+ INSTANTSPLAT_SPACE: HuggingFace Space URL (default: use local)
12
+ """
13
+
14
+ import sys
15
+ import os
16
+ from gradio_client import Client
17
+
18
+
19
+ def process_images(image_paths, space_url=None):
20
+ """
21
+ Submit images to InstantSplat and get GLB URL.
22
+
23
+ Args:
24
+ image_paths: List of image file paths
25
+ space_url: HuggingFace Space URL (optional)
26
+
27
+ Returns:
28
+ dict with results
29
+ """
30
+ # Validate inputs
31
+ if len(image_paths) < 2:
32
+ return {
33
+ "status": "error",
34
+ "error": "Need at least 2 images (3+ recommended)"
35
+ }
36
+
37
+ for path in image_paths:
38
+ if not os.path.exists(path):
39
+ return {
40
+ "status": "error",
41
+ "error": f"File not found: {path}"
42
+ }
43
+
44
+ try:
45
+ # Connect to Space
46
+ if space_url:
47
+ print(f"Connecting to Space: {space_url}")
48
+ client = Client(space_url)
49
+ else:
50
+ print("Using local Gradio instance (make sure it's running)")
51
+ client = Client("http://localhost:7860")
52
+
53
+ print(f"Submitting {len(image_paths)} images for processing...")
54
+ for i, img in enumerate(image_paths, 1):
55
+ print(f" {i}. {img}")
56
+
57
+ # Submit job
58
+ result = client.predict(
59
+ image_paths,
60
+ api_name="/predict"
61
+ )
62
+
63
+ # Unpack results
64
+ video_path, ply_url, _, _, glb_path, glb_url = result
65
+
66
+ # Check if upload succeeded
67
+ if glb_url and not glb_url.startswith("Error"):
68
+ return {
69
+ "status": "success",
70
+ "glb_url": glb_url,
71
+ "ply_url": ply_url,
72
+ "video_available": video_path is not None,
73
+ "message": "Processing complete!"
74
+ }
75
+ else:
76
+ return {
77
+ "status": "error",
78
+ "error": glb_url or "Upload failed"
79
+ }
80
+
81
+ except Exception as e:
82
+ return {
83
+ "status": "error",
84
+ "error": str(e)
85
+ }
86
+
87
+
88
+ def main():
89
+ """CLI interface."""
90
+ if len(sys.argv) < 3:
91
+ print("Usage: python api_client.py <image1> <image2> [image3 ...]")
92
+ print("\nExample:")
93
+ print(" python api_client.py img1.jpg img2.jpg img3.jpg")
94
+ print("\nEnvironment Variables:")
95
+ print(" INSTANTSPLAT_SPACE - HuggingFace Space URL (optional)")
96
+ print(" e.g., your-username/InstantSplat")
97
+ sys.exit(1)
98
+
99
+ # Get images from command line
100
+ image_paths = sys.argv[1:]
101
+
102
+ # Get Space URL from environment or use local
103
+ space_url = os.environ.get("INSTANTSPLAT_SPACE")
104
+
105
+ print("=" * 80)
106
+ print("InstantSplat API Client")
107
+ print("=" * 80)
108
+
109
+ # Process images
110
+ result = process_images(image_paths, space_url)
111
+
112
+ print("\n" + "=" * 80)
113
+
114
+ # Display results
115
+ if result["status"] == "success":
116
+ print("✅ SUCCESS!")
117
+ print("-" * 80)
118
+ print(f"GLB URL: {result['glb_url']}")
119
+ print(f"PLY URL: {result['ply_url']}")
120
+ if result['video_available']:
121
+ print("Video: Available")
122
+ print("-" * 80)
123
+ print("\n💡 Tip: You can now download the GLB file:")
124
+ print(f" curl -o model.glb '{result['glb_url']}'")
125
+ print("=" * 80)
126
+ return 0
127
+ else:
128
+ print("❌ ERROR!")
129
+ print("-" * 80)
130
+ print(f"Error: {result['error']}")
131
+ print("=" * 80)
132
+ return 1
133
+
134
+
135
+ if __name__ == "__main__":
136
+ sys.exit(main())
137
+
app.py CHANGED
@@ -392,11 +392,33 @@ def process(inputfiles, input_path=None):
392
  # 3) ply file path (for gr.File download)
393
  # 4) ply file path (for gr.Model3D viewer)
394
  # 5) glb file path (for gr.Model3D viewer)
395
- return output_video_path, ply_url, output_ply_path, output_ply_path, output_glb_path
 
396
  ##################################################################################################################################################
397
 
398
 
399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  _TITLE = '''InstantSplat'''
401
  _DESCRIPTION = '''
402
  <div style="display: flex; justify-content: center; align-items: center;">
@@ -433,6 +455,68 @@ with block:
433
  inputfiles = gr.File(file_count="multiple", label="images")
434
  input_path = gr.Textbox(visible=False, label="example_path")
435
  button_gen = gr.Button("RUN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
  with gr.Row(variant='panel'):
438
  with gr.Tab("Output"):
@@ -466,17 +550,24 @@ with block:
466
  with gr.Column(scale=1):
467
  output_video = gr.Video(label="video")
468
 
469
- button_gen.click(process, inputs=[inputfiles], outputs=[output_video, output_file, output_download, output_model_ply, output_model_glb])
 
 
 
 
 
 
 
470
 
471
  gr.Examples(
472
  examples=[
473
  "sora-santorini-3-views",
474
  ],
475
  inputs=[input_path],
476
- outputs=[output_video, output_file, output_download, output_model_ply, output_model_glb],
477
  fn=lambda x: process(inputfiles=None, input_path=x),
478
  cache_examples=True,
479
  label='Sparse-view Examples'
480
  )
481
 
482
- block.launch(server_name="0.0.0.0", share=False)
 
392
  # 3) ply file path (for gr.File download)
393
  # 4) ply file path (for gr.Model3D viewer)
394
  # 5) glb file path (for gr.Model3D viewer)
395
+ # 6) glb URL (for API)
396
+ return output_video_path, ply_url, output_ply_path, output_ply_path, output_glb_path, glb_url
397
  ##################################################################################################################################################
398
 
399
 
400
 
401
+ def process_api(inputfiles):
402
+ """
403
+ API-friendly wrapper that returns only the GLB URL.
404
+
405
+ Args:
406
+ inputfiles: List of image files
407
+
408
+ Returns:
409
+ dict with glb_url, ply_url, and video_url
410
+ """
411
+ result = process(inputfiles, input_path=None)
412
+ video_path, ply_url, _, _, glb_path, glb_url = result
413
+
414
+ return {
415
+ "glb_url": glb_url if glb_url else "Upload failed",
416
+ "ply_url": ply_url if ply_url else "Upload failed",
417
+ "video_available": video_path is not None,
418
+ "status": "success" if glb_url else "error"
419
+ }
420
+
421
+
422
  _TITLE = '''InstantSplat'''
423
  _DESCRIPTION = '''
424
  <div style="display: flex; justify-content: center; align-items: center;">
 
455
  inputfiles = gr.File(file_count="multiple", label="images")
456
  input_path = gr.Textbox(visible=False, label="example_path")
457
  button_gen = gr.Button("RUN")
458
+
459
+ with gr.Tab("API"):
460
+ gr.Markdown("""
461
+ ## 🚀 API Access
462
+
463
+ Submit images programmatically and get back the Supabase GLB URL.
464
+
465
+ ### Quick Start (Python)
466
+
467
+ ```bash
468
+ pip install gradio_client
469
+ ```
470
+
471
+ ```python
472
+ from gradio_client import Client
473
+
474
+ # Connect to this Space
475
+ client = Client("your-username/InstantSplat")
476
+
477
+ # Submit images
478
+ result = client.predict(
479
+ ["image1.jpg", "image2.jpg", "image3.jpg"],
480
+ api_name="/predict"
481
+ )
482
+
483
+ # Get GLB URL (it's the 6th element)
484
+ glb_url = result[5]
485
+ print(f"GLB URL: {glb_url}")
486
+ ```
487
+
488
+ ### Response Format
489
+
490
+ The API returns a tuple with 6 elements:
491
+ - `[0]` - Video path
492
+ - `[1]` - PLY URL (Supabase)
493
+ - `[2]` - PLY download path
494
+ - `[3]` - PLY model path
495
+ - `[4]` - GLB model path
496
+ - `[5]` - **GLB URL (Supabase)** ← This is what you want!
497
+
498
+ ### CLI Tool
499
+
500
+ Use the included `api_client.py`:
501
+
502
+ ```bash
503
+ python api_client.py img1.jpg img2.jpg img3.jpg
504
+ ```
505
+
506
+ ### Full Documentation
507
+
508
+ See `API_GUIDE.md` for complete documentation including:
509
+ - JavaScript/TypeScript examples
510
+ - Error handling
511
+ - Batch processing
512
+ - Complete workflows
513
+
514
+ ### Requirements
515
+
516
+ - **Minimum**: 2 images (3+ recommended)
517
+ - **Same resolution**: All images must have matching dimensions
518
+ - **Formats**: JPG, PNG
519
+ """)
520
 
521
  with gr.Row(variant='panel'):
522
  with gr.Tab("Output"):
 
550
  with gr.Column(scale=1):
551
  output_video = gr.Video(label="video")
552
 
553
+ # Hidden output for GLB URL (for API access)
554
+ output_glb_url = gr.Textbox(visible=False, label="GLB URL")
555
+
556
+ button_gen.click(
557
+ process,
558
+ inputs=[inputfiles],
559
+ outputs=[output_video, output_file, output_download, output_model_ply, output_model_glb, output_glb_url]
560
+ )
561
 
562
  gr.Examples(
563
  examples=[
564
  "sora-santorini-3-views",
565
  ],
566
  inputs=[input_path],
567
+ outputs=[output_video, output_file, output_download, output_model_ply, output_model_glb, output_glb_url],
568
  fn=lambda x: process(inputfiles=None, input_path=x),
569
  cache_examples=True,
570
  label='Sparse-view Examples'
571
  )
572
 
573
+ block.launch(server_name="0.0.0.0", share=False, show_api=True)
env.example ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Supabase Storage Configuration
2
+ # Copy this file to .env and fill in your values
3
+
4
+ # Your Supabase project URL
5
+ # Example: https://abcdefghijklmnop.supabase.co
6
+ SUPABASE_URL=https://your-project-id.supabase.co
7
+
8
+ # Your Supabase service role key (for server-side uploads)
9
+ # Get this from: Supabase Dashboard > Settings > API > service_role key
10
+ # WARNING: Keep this secret! Never commit this to version control.
11
+ SUPABASE_KEY=your-service-role-key-here
12
+
13
+ # Storage bucket name (optional, defaults to "outputs")
14
+ SUPABASE_BUCKET=outputs
15
+
test_supabase_upload.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for Supabase Storage upload functionality.
4
+
5
+ Usage:
6
+ python test_supabase_upload.py
7
+
8
+ Make sure to set environment variables before running:
9
+ export SUPABASE_URL="https://your-project-id.supabase.co"
10
+ export SUPABASE_KEY="your-service-role-key"
11
+ export SUPABASE_BUCKET="outputs" # optional
12
+ """
13
+
14
+ import os
15
+ import sys
16
+ import tempfile
17
+ import time
18
+
19
+
20
+ def create_test_file(size_mb: int = 10) -> str:
21
+ """Create a temporary test file of specified size in MB."""
22
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.bin')
23
+
24
+ # Write random data
25
+ chunk_size = 1024 * 1024 # 1 MB
26
+ for _ in range(size_mb):
27
+ temp_file.write(os.urandom(chunk_size))
28
+
29
+ temp_file.close()
30
+ print(f"Created test file: {temp_file.name} ({size_mb} MB)")
31
+ return temp_file.name
32
+
33
+
34
+ def main():
35
+ # Import the upload function from app.py
36
+ try:
37
+ # Add parent directory to path
38
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
39
+ from app import upload_to_supabase_storage
40
+ except ImportError as e:
41
+ print(f"Error importing upload function: {e}")
42
+ print("Make sure app.py is in the same directory.")
43
+ return 1
44
+
45
+ # Check environment variables
46
+ supabase_url = os.environ.get("SUPABASE_URL")
47
+ supabase_key = os.environ.get("SUPABASE_KEY")
48
+ supabase_bucket = os.environ.get("SUPABASE_BUCKET", "outputs")
49
+
50
+ if not supabase_url:
51
+ print("ERROR: SUPABASE_URL environment variable not set")
52
+ print("Set it with: export SUPABASE_URL='https://your-project-id.supabase.co'")
53
+ return 1
54
+
55
+ if not supabase_key:
56
+ print("ERROR: SUPABASE_KEY environment variable not set")
57
+ print("Set it with: export SUPABASE_KEY='your-service-role-key'")
58
+ return 1
59
+
60
+ print("=" * 80)
61
+ print("Supabase Storage Upload Test")
62
+ print("=" * 80)
63
+ print(f"URL: {supabase_url}")
64
+ print(f"Bucket: {supabase_bucket}")
65
+ print(f"Key: {supabase_key[:20]}... (truncated)")
66
+ print("=" * 80)
67
+
68
+ # Test 1: Small file upload (1 MB)
69
+ print("\nTest 1: Small file upload (1 MB)")
70
+ print("-" * 80)
71
+ test_file_small = create_test_file(1)
72
+
73
+ try:
74
+ start_time = time.time()
75
+ url = upload_to_supabase_storage(
76
+ file_path=test_file_small,
77
+ remote_path="test/small_file.bin",
78
+ supabase_url=supabase_url,
79
+ supabase_key=supabase_key,
80
+ bucket_name=supabase_bucket
81
+ )
82
+ elapsed = time.time() - start_time
83
+
84
+ if url:
85
+ print(f"✅ SUCCESS: Uploaded in {elapsed:.2f} seconds")
86
+ print(f"URL: {url}")
87
+ else:
88
+ print("❌ FAILED: Upload returned None")
89
+ except Exception as e:
90
+ print(f"❌ ERROR: {e}")
91
+ finally:
92
+ os.unlink(test_file_small)
93
+
94
+ # Test 2: Medium file upload (50 MB)
95
+ print("\nTest 2: Medium file upload (50 MB)")
96
+ print("-" * 80)
97
+ test_file_medium = create_test_file(50)
98
+
99
+ try:
100
+ start_time = time.time()
101
+ url = upload_to_supabase_storage(
102
+ file_path=test_file_medium,
103
+ remote_path="test/medium_file.bin",
104
+ supabase_url=supabase_url,
105
+ supabase_key=supabase_key,
106
+ bucket_name=supabase_bucket
107
+ )
108
+ elapsed = time.time() - start_time
109
+
110
+ if url:
111
+ print(f"✅ SUCCESS: Uploaded in {elapsed:.2f} seconds")
112
+ print(f"URL: {url}")
113
+ else:
114
+ print("❌ FAILED: Upload returned None")
115
+ except Exception as e:
116
+ print(f"❌ ERROR: {e}")
117
+ finally:
118
+ os.unlink(test_file_medium)
119
+
120
+ # Test 3: Large file upload (200 MB) - optional
121
+ print("\nTest 3: Large file upload (200 MB) - Optional")
122
+ print("-" * 80)
123
+ response = input("Run large file test? This will create and upload a 200MB file (y/N): ")
124
+
125
+ if response.lower() == 'y':
126
+ test_file_large = create_test_file(200)
127
+
128
+ try:
129
+ start_time = time.time()
130
+ url = upload_to_supabase_storage(
131
+ file_path=test_file_large,
132
+ remote_path="test/large_file.bin",
133
+ supabase_url=supabase_url,
134
+ supabase_key=supabase_key,
135
+ bucket_name=supabase_bucket
136
+ )
137
+ elapsed = time.time() - start_time
138
+
139
+ if url:
140
+ print(f"✅ SUCCESS: Uploaded in {elapsed:.2f} seconds")
141
+ print(f"Upload speed: {200 / elapsed:.2f} MB/s")
142
+ print(f"URL: {url}")
143
+ else:
144
+ print("❌ FAILED: Upload returned None")
145
+ except Exception as e:
146
+ print(f"❌ ERROR: {e}")
147
+ finally:
148
+ os.unlink(test_file_large)
149
+ else:
150
+ print("Skipped large file test")
151
+
152
+ print("\n" + "=" * 80)
153
+ print("Testing complete!")
154
+ print("=" * 80)
155
+
156
+ return 0
157
+
158
+
159
+ if __name__ == "__main__":
160
+ sys.exit(main())
161
+