hann1010 commited on
Commit
5e16c59
·
verified ·
1 Parent(s): 4076b22

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +633 -465
app.py CHANGED
@@ -1,466 +1,634 @@
1
- import gradio as gr
2
- import torch
3
- from PIL import Image
4
- from torchvision import transforms
5
- from transformers import AutoModelForImageSegmentation
6
- import json
7
- import logging
8
- import os
9
- from pathlib import Path
10
- import io
11
- import base64
12
-
13
- logging.basicConfig(level=logging.INFO)
14
- logger = logging.getLogger(__name__)
15
-
16
- # ========== KONFIGURASI ==========
17
- MODEL_ID = "mohantesting/remove_background"
18
- MODEL_PATH = "./models/remove_background"
19
- # ==================================
20
-
21
- model = None
22
- device = None
23
- transform_image = None
24
-
25
- def check_model_exists(path):
26
- """Cek apakah model sudah ada"""
27
- if not os.path.exists(path):
28
- return False
29
-
30
- required_files = ["config.json"]
31
- for file in required_files:
32
- if not os.path.exists(os.path.join(path, file)):
33
- return False
34
-
35
- has_weights = False
36
- for root, dirs, files in os.walk(path):
37
- for file in files:
38
- if file.endswith((".bin", ".safetensors")):
39
- has_weights = True
40
- break
41
- if has_weights:
42
- break
43
-
44
- return has_weights
45
-
46
- def get_folder_size(folder_path):
47
- """Hitung total ukuran folder"""
48
- total_size = 0
49
- for dirpath, dirnames, filenames in os.walk(folder_path):
50
- for filename in filenames:
51
- filepath = os.path.join(dirpath, filename)
52
- if os.path.isfile(filepath):
53
- total_size += os.path.getsize(filepath)
54
- return total_size
55
-
56
- def download_model():
57
- """Download model jika belum ada"""
58
- logger.info("="*60)
59
- logger.info("CHECKING BACKGROUND REMOVAL MODEL...")
60
- logger.info("="*60)
61
-
62
- if check_model_exists(MODEL_PATH):
63
- logger.info("✓ Model sudah ada di local!")
64
- logger.info(f" Location: {MODEL_PATH}")
65
-
66
- size_bytes = get_folder_size(MODEL_PATH)
67
- size_mb = size_bytes / (1024 * 1024)
68
- logger.info(f"✓ Size: {size_mb:.2f} MB")
69
- logger.info("✓ Skipping download...\n")
70
- return True
71
-
72
- logger.info("✗ Model tidak ditemukan. Mulai download...")
73
- logger.info(f"Model ID: {MODEL_ID}")
74
- logger.info(f"Save to: {MODEL_PATH}")
75
- logger.info("-" * 60)
76
-
77
- try:
78
- os.makedirs(MODEL_PATH, exist_ok=True)
79
-
80
- logger.info("Downloading background removal model...")
81
- model_download = AutoModelForImageSegmentation.from_pretrained(
82
- MODEL_ID,
83
- trust_remote_code=True
84
- )
85
- model_download.save_pretrained(MODEL_PATH)
86
-
87
- logger.info(" Model downloaded\n")
88
-
89
- size_bytes = get_folder_size(MODEL_PATH)
90
- size_mb = size_bytes / (1024 * 1024)
91
- logger.info(f"✓ Total size: {size_mb:.2f} MB")
92
- logger.info(f"✓ Model saved at: {MODEL_PATH}\n")
93
-
94
- del model_download
95
- torch.cuda.empty_cache() if torch.cuda.is_available() else None
96
-
97
- return True
98
-
99
- except Exception as e:
100
- logger.error(f"✗ Error downloading model: {str(e)}")
101
- import traceback
102
- traceback.print_exc()
103
- return False
104
-
105
- def load_model():
106
- """Load model ke memory"""
107
- global model, device, transform_image
108
-
109
- logger.info("="*60)
110
- logger.info("LOADING MODEL INTO MEMORY...")
111
- logger.info("="*60)
112
-
113
- try:
114
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
115
- logger.info(f"Device: {device}")
116
-
117
- logger.info("Loading model from local...")
118
- model = AutoModelForImageSegmentation.from_pretrained(
119
- MODEL_PATH,
120
- trust_remote_code=True
121
- ).eval().to(device)
122
-
123
- # Setup transform
124
- image_size = (1024, 1024)
125
- transform_image = transforms.Compose([
126
- transforms.Resize(image_size),
127
- transforms.ToTensor(),
128
- transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
129
- ])
130
-
131
- logger.info("="*60)
132
- logger.info("✓ MODEL READY!")
133
- logger.info(f" Model: {MODEL_ID}")
134
- logger.info(f" Device: {device}")
135
- logger.info(f" Image Size: {image_size}")
136
- logger.info("="*60 + "\n")
137
-
138
- return True
139
-
140
- except Exception as e:
141
- logger.error(f"✗ Failed to load model: {str(e)}")
142
- import traceback
143
- traceback.print_exc()
144
- return False
145
-
146
- # ========== STARTUP SEQUENCE ==========
147
- logger.info("Starting application...")
148
-
149
- if not download_model():
150
- raise Exception("Failed to download model")
151
-
152
- if not load_model():
153
- raise Exception("Failed to load model into memory")
154
-
155
- # ========================================
156
-
157
- def remove_background(input_image):
158
- """Remove background dari image"""
159
-
160
- try:
161
- if model is None or transform_image is None:
162
- return None, None, json.dumps({
163
- "success": False,
164
- "error": "Model belum siap"
165
- }, indent=2, ensure_ascii=False)
166
-
167
- if input_image is None:
168
- return None, None, json.dumps({
169
- "success": False,
170
- "error": "Image tidak boleh kosong"
171
- }, indent=2, ensure_ascii=False)
172
-
173
- # Convert to PIL Image
174
- if not isinstance(input_image, Image.Image):
175
- input_image = Image.fromarray(input_image).convert("RGB")
176
- else:
177
- input_image = input_image.convert("RGB")
178
-
179
- logger.info(f"Processing image... Size: {input_image.width}x{input_image.height}")
180
-
181
- # Transform image
182
- input_tensor = transform_image(input_image).unsqueeze(0).to(device)
183
-
184
- # Prediction
185
- with torch.no_grad():
186
- preds = model(input_tensor)[-1].sigmoid().cpu()
187
-
188
- pred = preds[0].squeeze()
189
- pred_pil = transforms.ToPILImage()(pred)
190
- mask = pred_pil.resize(input_image.size)
191
-
192
- # Create output with alpha channel
193
- output_image = input_image.copy()
194
- output_image.putalpha(mask)
195
-
196
- logger.info(f" Background removed. Output: {output_image.width}x{output_image.height}")
197
-
198
- # JSON result
199
- result = {
200
- "success": True,
201
- "input_size": f"{input_image.width}x{input_image.height}",
202
- "output_size": f"{output_image.width}x{output_image.height}",
203
- "output_format": "PNG with alpha channel",
204
- "model": MODEL_ID,
205
- "device": device
206
- }
207
-
208
- return output_image, mask, json.dumps(result, indent=2, ensure_ascii=False)
209
-
210
- except Exception as e:
211
- logger.error(f"Error removing background: {str(e)}", exc_info=True)
212
- return None, None, json.dumps({
213
- "success": False,
214
- "error": str(e)
215
- }, indent=2, ensure_ascii=False)
216
-
217
- def get_model_info():
218
- """Return model info sebagai JSON"""
219
- try:
220
- info = {
221
- "model_name": "Background Removal Model",
222
- "model_id": MODEL_ID,
223
- "model_path": MODEL_PATH,
224
- "model_type": "Image Segmentation",
225
- "device": device if device else "unknown",
226
- "model_loaded": model is not None,
227
- "image_processing_size": "1024x1024",
228
- "output_format": "PNG with transparency (alpha channel)",
229
- "capabilities": [
230
- "Automatic background removal",
231
- "High-quality segmentation",
232
- "Preserve original image resolution",
233
- "Generate alpha mask"
234
- ],
235
- "use_cases": [
236
- "Product photography",
237
- "Portrait editing",
238
- "E-commerce images",
239
- "Graphic design",
240
- "Social media content"
241
- ]
242
- }
243
-
244
- return json.dumps(info, indent=2, ensure_ascii=False)
245
- except Exception as e:
246
- return json.dumps({"error": str(e)}, indent=2, ensure_ascii=False)
247
-
248
- # Custom CSS
249
- custom_css = """
250
- #output_json {
251
- font-family: 'Courier New', monospace;
252
- font-size: 14px;
253
- }
254
- .gradio-container {
255
- max-width: 1600px !important;
256
- }
257
- """
258
-
259
- # Gradio Interface
260
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
261
- gr.Markdown("""
262
- # 🎨 Background Removal API
263
- AI-powered automatic background removal using image segmentation
264
- """)
265
-
266
- with gr.Tabs():
267
- # Tab Background Removal
268
- with gr.Tab("✂️ Remove Background"):
269
- with gr.Row():
270
- with gr.Column(scale=1):
271
- input_image = gr.Image(
272
- label="📸 Input Image",
273
- type="pil",
274
- height=450
275
- )
276
-
277
- remove_btn = gr.Button(
278
- "✂️ Remove Background",
279
- variant="primary",
280
- size="lg"
281
- )
282
-
283
- with gr.Column(scale=1):
284
- output_image = gr.Image(
285
- label="🖼️ Output (No Background)",
286
- type="pil",
287
- height=450
288
- )
289
-
290
- output_mask = gr.Image(
291
- label="🎭 Alpha Mask",
292
- type="pil",
293
- height=200
294
- )
295
-
296
- output_json = gr.Code(
297
- label="📄 JSON Output",
298
- language="json",
299
- lines=10,
300
- elem_id="output_json"
301
- )
302
-
303
- remove_btn.click(
304
- fn=remove_background,
305
- inputs=[input_image],
306
- outputs=[output_image, output_mask, output_json]
307
- )
308
-
309
- # Tab Model Info
310
- with gr.Tab("ℹ️ Model Info"):
311
- model_info_output = gr.Code(
312
- label="Model Information",
313
- language="json",
314
- lines=30
315
- )
316
- info_btn = gr.Button("🔍 Get Model Info", variant="secondary")
317
-
318
- info_btn.click(
319
- fn=get_model_info,
320
- inputs=[],
321
- outputs=model_info_output
322
- )
323
-
324
- # Tab API Documentation
325
- with gr.Tab("📚 API Usage"):
326
- gr.Markdown("""
327
- ## 🚀 API Usage Guide
328
-
329
- ### 1. Python Example
330
- ```python
331
- import requests
332
- import base64
333
- from PIL import Image
334
- from io import BytesIO
335
- import json
336
-
337
- # Load and encode image
338
- with open("input.jpg", "rb") as f:
339
- img_data = base64.b64encode(f.read()).decode()
340
-
341
- url = "https://YOUR-SPACE-URL/api/predict"
342
-
343
- payload = {
344
- "data": [f"data:image/jpeg;base64,{img_data}"]
345
- }
346
-
347
- response = requests.post(url, json=payload)
348
- result = response.json()
349
-
350
- # Get output image (PNG with transparency)
351
- output_image_data = result['data'][0]
352
- output_json = json.loads(result['data'][2])
353
-
354
- # Decode and save
355
- img_bytes = base64.b64decode(output_image_data.split(',')[1])
356
- img = Image.open(BytesIO(img_bytes))
357
- img.save('output_no_bg.png')
358
-
359
- print(json.dumps(output_json, indent=2))
360
- ```
361
-
362
- ### 2. Response Format
363
- ```json
364
- {
365
- "success": true,
366
- "input_size": "1200x1600",
367
- "output_size": "1200x1600",
368
- "output_format": "PNG with alpha channel",
369
- "model": "mohantesting/remove_background",
370
- "device": "cuda"
371
- }
372
- ```
373
-
374
- ### 3. Output Format
375
-
376
- - **Format**: PNG with transparency (alpha channel)
377
- - **Resolution**: Same as input image
378
- - **Background**: Completely transparent
379
- - **Quality**: High-quality segmentation
380
-
381
- ### 4. Best Practices
382
-
383
- ✅ **DO:**
384
- - Use high-resolution images for better results
385
- - Ensure good contrast between subject and background
386
- - Use well-lit images
387
- - Save output as PNG to preserve transparency
388
-
389
- **DON'T:**
390
- - Don't use extremely large images (>4K) - may cause OOM
391
- - Don't expect perfect results on complex backgrounds
392
- - Don't save as JPEG (loses transparency)
393
-
394
- ### 5. Use Cases
395
-
396
- **E-commerce Product Photos:**
397
- ```python
398
- # Remove background from product image
399
- result = remove_background('product.jpg')
400
- result.save('product_no_bg.png')
401
- ```
402
-
403
- **Portrait Photography:**
404
- ```python
405
- # Remove background from portrait
406
- result = remove_background('portrait.jpg')
407
- # Can now composite on different backgrounds
408
- ```
409
-
410
- **Graphic Design:**
411
- ```python
412
- # Create cutouts for design work
413
- result = remove_background('subject.jpg')
414
- # Use in Photoshop, Canva, etc.
415
- ```
416
-
417
- ### 6. Integration Example
418
-
419
- **Batch Processing:**
420
- ```python
421
- import os
422
- from pathlib import Path
423
-
424
- input_dir = 'input_images'
425
- output_dir = 'output_images'
426
-
427
- os.makedirs(output_dir, exist_ok=True)
428
-
429
- for img_file in Path(input_dir).glob('*.jpg'):
430
- result = remove_background(str(img_file))
431
- output_path = Path(output_dir) / f"{img_file.stem}_no_bg.png"
432
- result.save(output_path)
433
- print(f"Processed: {img_file.name}")
434
- ```
435
-
436
- ### 7. Performance
437
-
438
- - **Processing Time**: ~1-3 seconds per image (GPU)
439
- - **Max Resolution**: Recommended up to 2048x2048
440
- - **Model Size**: ~180MB
441
- - **GPU Memory**: ~2GB recommended
442
-
443
- ---
444
-
445
- **Model:** mohantesting/remove_background
446
- **Type:** Image Segmentation
447
- **Framework:** PyTorch + Transformers
448
- """)
449
-
450
- gr.Markdown("""
451
- ---
452
- ### 💡 Tips for Best Results
453
-
454
- - Use images with **clear subject-background separation**
455
- - **Good lighting** improves accuracy
456
- - **Higher resolution** = better edge quality
457
- - Save as **PNG** to preserve transparency
458
- """)
459
-
460
- # Launch
461
- if __name__ == "__main__":
462
- demo.launch(
463
- server_name="0.0.0.0",
464
- server_port=7860,
465
- share=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  )
 
1
+ import gradio as gr
2
+ import torch
3
+ from PIL import Image
4
+ from torchvision import transforms
5
+ from transformers import AutoModelForImageSegmentation
6
+ import json
7
+ import logging
8
+ import os
9
+ from pathlib import Path
10
+ import sys
11
+ from datetime import datetime
12
+
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # ========== KONFIGURASI ==========
17
+ MODEL_ID = "mohantesting/remove_background"
18
+ MODEL_PATH = "./models/remove_background"
19
+ MAX_IMAGE_SIZE = 2048
20
+ PROCESSING_SIZE = (1024, 1024)
21
+ # ==================================
22
+
23
+ model = None
24
+ device = None
25
+ transform_image = None
26
+ stats = {
27
+ "total_processed": 0,
28
+ "total_errors": 0,
29
+ "start_time": datetime.now()
30
+ }
31
+
32
+ def check_model_exists(path):
33
+ """Cek apakah model sudah ada"""
34
+ if not os.path.exists(path):
35
+ return False
36
+
37
+ required_files = ["config.json"]
38
+ for file in required_files:
39
+ if not os.path.exists(os.path.join(path, file)):
40
+ return False
41
+
42
+ has_weights = False
43
+ for root, dirs, files in os.walk(path):
44
+ for file in files:
45
+ if file.endswith((".bin", ".safetensors")):
46
+ has_weights = True
47
+ break
48
+ if has_weights:
49
+ break
50
+
51
+ return has_weights
52
+
53
+ def get_folder_size(folder_path):
54
+ """Hitung total ukuran folder"""
55
+ total_size = 0
56
+ for dirpath, dirnames, filenames in os.walk(folder_path):
57
+ for filename in filenames:
58
+ filepath = os.path.join(dirpath, filename)
59
+ if os.path.isfile(filepath):
60
+ total_size += os.path.getsize(filepath)
61
+ return total_size
62
+
63
+ def download_model():
64
+ """Download model jika belum ada"""
65
+ logger.info("="*60)
66
+ logger.info("CHECKING BACKGROUND REMOVAL MODEL...")
67
+ logger.info("="*60)
68
+
69
+ if check_model_exists(MODEL_PATH):
70
+ logger.info("✓ Model sudah ada di local!")
71
+ logger.info(f"✓ Location: {MODEL_PATH}")
72
+
73
+ size_bytes = get_folder_size(MODEL_PATH)
74
+ size_mb = size_bytes / (1024 * 1024)
75
+ logger.info(f" Size: {size_mb:.2f} MB")
76
+ logger.info("✓ Skipping download...\n")
77
+ return True
78
+
79
+ logger.info("✗ Model tidak ditemukan. Mulai download...")
80
+ logger.info(f"Model ID: {MODEL_ID}")
81
+ logger.info(f"Save to: {MODEL_PATH}")
82
+ logger.info("-" * 60)
83
+
84
+ try:
85
+ os.makedirs(MODEL_PATH, exist_ok=True)
86
+
87
+ logger.info("Downloading background removal model...")
88
+ # Download langsung tanpa save - kita akan load langsung dari HF
89
+ # karena model ini menggunakan custom code (BiRefNet)
90
+
91
+ logger.info("✓ Model akan di-load langsung dari HuggingFace\n")
92
+ return True
93
+
94
+ except Exception as e:
95
+ logger.error(f"✗ Error: {str(e)}")
96
+ import traceback
97
+ traceback.print_exc()
98
+ return False
99
+
100
+ def load_model():
101
+ """Load model ke memory"""
102
+ global model, device, transform_image
103
+
104
+ logger.info("="*60)
105
+ logger.info("LOADING MODEL INTO MEMORY...")
106
+ logger.info("="*60)
107
+
108
+ try:
109
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
110
+ logger.info(f"Device: {device}")
111
+
112
+ # Coba load dari local dulu, kalau gagal load dari HuggingFace
113
+ try:
114
+ if check_model_exists(MODEL_PATH):
115
+ logger.info("Attempting to load from local...")
116
+ # Add local path to sys.path for custom modules
117
+ if MODEL_PATH not in sys.path:
118
+ sys.path.insert(0, MODEL_PATH)
119
+
120
+ model = AutoModelForImageSegmentation.from_pretrained(
121
+ MODEL_PATH,
122
+ trust_remote_code=True,
123
+ local_files_only=True
124
+ )
125
+ logger.info("✓ Loaded from local cache")
126
+ else:
127
+ raise FileNotFoundError("Local model not found")
128
+
129
+ except Exception as e:
130
+ logger.info(f"Local load failed: {str(e)}")
131
+ logger.info("Loading from HuggingFace Hub...")
132
+ model = AutoModelForImageSegmentation.from_pretrained(
133
+ MODEL_ID,
134
+ trust_remote_code=True
135
+ )
136
+ logger.info(" Loaded from HuggingFace Hub")
137
+
138
+ # Save untuk next time
139
+ try:
140
+ logger.info("Saving model to local cache...")
141
+ model.save_pretrained(MODEL_PATH)
142
+ logger.info(f"✓ Model saved to {MODEL_PATH}")
143
+ except Exception as save_err:
144
+ logger.warning(f"Could not save model: {save_err}")
145
+
146
+ model.eval().to(device)
147
+
148
+ # Setup transform
149
+ transform_image = transforms.Compose([
150
+ transforms.Resize(PROCESSING_SIZE),
151
+ transforms.ToTensor(),
152
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
153
+ ])
154
+
155
+ logger.info("="*60)
156
+ logger.info("✓ MODEL READY!")
157
+ logger.info(f" Model: {MODEL_ID}")
158
+ logger.info(f" Device: {device}")
159
+ logger.info(f" Processing Size: {PROCESSING_SIZE}")
160
+ logger.info("="*60 + "\n")
161
+
162
+ return True
163
+
164
+ except Exception as e:
165
+ logger.error(f"✗ Failed to load model: {str(e)}")
166
+ import traceback
167
+ traceback.print_exc()
168
+ return False
169
+
170
+ # ========== STARTUP SEQUENCE ==========
171
+ logger.info("\n" + "="*60)
172
+ logger.info(f" APPLICATION STARTUP - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
173
+ logger.info("="*60 + "\n")
174
+
175
+ if not download_model():
176
+ raise Exception("Failed to prepare model")
177
+
178
+ if not load_model():
179
+ raise Exception("Failed to load model into memory")
180
+
181
+ # ========================================
182
+
183
+ def remove_background(input_image):
184
+ """Remove background dari image"""
185
+
186
+ try:
187
+ if model is None or transform_image is None:
188
+ return None, None, json.dumps({
189
+ "success": False,
190
+ "error": "Model belum siap"
191
+ }, indent=2, ensure_ascii=False)
192
+
193
+ if input_image is None:
194
+ return None, None, json.dumps({
195
+ "success": False,
196
+ "error": "Image tidak boleh kosong"
197
+ }, indent=2, ensure_ascii=False)
198
+
199
+ # Convert to PIL Image
200
+ if not isinstance(input_image, Image.Image):
201
+ input_image = Image.fromarray(input_image).convert("RGB")
202
+ else:
203
+ input_image = input_image.convert("RGB")
204
+
205
+ original_size = input_image.size
206
+ logger.info(f"Processing image... Size: {original_size[0]}x{original_size[1]}")
207
+
208
+ # Check if image is too large
209
+ max_dim = max(original_size)
210
+ if max_dim > MAX_IMAGE_SIZE:
211
+ scale = MAX_IMAGE_SIZE / max_dim
212
+ new_size = (int(original_size[0] * scale), int(original_size[1] * scale))
213
+ logger.info(f"Resizing large image to {new_size[0]}x{new_size[1]}")
214
+ input_image = input_image.resize(new_size, Image.Resampling.LANCZOS)
215
+
216
+ # Transform image
217
+ input_tensor = transform_image(input_image).unsqueeze(0).to(device)
218
+
219
+ # Prediction
220
+ with torch.no_grad():
221
+ preds = model(input_tensor)[-1].sigmoid().cpu()
222
+
223
+ pred = preds[0].squeeze()
224
+ pred_pil = transforms.ToPILImage()(pred)
225
+ mask = pred_pil.resize(input_image.size, Image.Resampling.LANCZOS)
226
+
227
+ # Create output with alpha channel
228
+ output_image = input_image.copy()
229
+ output_image.putalpha(mask)
230
+
231
+ # Update stats
232
+ stats["total_processed"] += 1
233
+
234
+ logger.info(f"✓ Background removed. Output: {output_image.width}x{output_image.height}")
235
+
236
+ # JSON result
237
+ result = {
238
+ "success": True,
239
+ "input_size": f"{input_image.width}x{input_image.height}",
240
+ "output_size": f"{output_image.width}x{output_image.height}",
241
+ "output_format": "PNG with alpha channel",
242
+ "model": MODEL_ID,
243
+ "device": device,
244
+ "processing_time": "~1-3 seconds"
245
+ }
246
+
247
+ return output_image, mask, json.dumps(result, indent=2, ensure_ascii=False)
248
+
249
+ except Exception as e:
250
+ stats["total_errors"] += 1
251
+ logger.error(f"Error removing background: {str(e)}", exc_info=True)
252
+ return None, None, json.dumps({
253
+ "success": False,
254
+ "error": str(e)
255
+ }, indent=2, ensure_ascii=False)
256
+
257
+ def get_model_info():
258
+ """Return model info sebagai JSON"""
259
+ try:
260
+ uptime = datetime.now() - stats["start_time"]
261
+
262
+ info = {
263
+ "model_name": "Background Removal Model (BiRefNet)",
264
+ "model_id": MODEL_ID,
265
+ "model_path": MODEL_PATH,
266
+ "model_type": "Image Segmentation",
267
+ "architecture": "BiRefNet (Bilateral Reference Network)",
268
+ "device": device if device else "unknown",
269
+ "model_loaded": model is not None,
270
+ "processing_size": f"{PROCESSING_SIZE[0]}x{PROCESSING_SIZE[1]}",
271
+ "max_input_size": f"{MAX_IMAGE_SIZE}x{MAX_IMAGE_SIZE}",
272
+ "output_format": "PNG with transparency (alpha channel)",
273
+ "statistics": {
274
+ "total_processed": stats["total_processed"],
275
+ "total_errors": stats["total_errors"],
276
+ "uptime": str(uptime).split('.')[0],
277
+ "success_rate": f"{((stats['total_processed'] - stats['total_errors']) / max(stats['total_processed'], 1) * 100):.1f}%"
278
+ },
279
+ "capabilities": [
280
+ "Automatic background removal",
281
+ "High-quality segmentation",
282
+ "Preserve original image resolution",
283
+ "Generate alpha mask",
284
+ "Handles complex backgrounds"
285
+ ],
286
+ "use_cases": [
287
+ "Product photography",
288
+ "Portrait editing",
289
+ "E-commerce images",
290
+ "Graphic design",
291
+ "Social media content",
292
+ "Profile pictures"
293
+ ],
294
+ "technical_details": {
295
+ "framework": "PyTorch + Transformers",
296
+ "trust_remote_code": True,
297
+ "normalization": "ImageNet stats",
298
+ "model_size": "~840MB"
299
+ }
300
+ }
301
+
302
+ return json.dumps(info, indent=2, ensure_ascii=False)
303
+ except Exception as e:
304
+ return json.dumps({"error": str(e)}, indent=2, ensure_ascii=False)
305
+
306
+ # Custom CSS
307
+ custom_css = """
308
+ #output_json {
309
+ font-family: 'Courier New', monospace;
310
+ font-size: 14px;
311
+ }
312
+ .gradio-container {
313
+ max-width: 1600px !important;
314
+ }
315
+ .tab-nav button {
316
+ font-size: 16px;
317
+ font-weight: 500;
318
+ }
319
+ """
320
+
321
+ # Gradio Interface
322
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
323
+ gr.Markdown("""
324
+ # 🎨 Background Removal API
325
+ ### AI-powered automatic background removal using BiRefNet
326
+ Remove backgrounds from images with high-quality segmentation
327
+ """)
328
+
329
+ with gr.Tabs():
330
+ # Tab Background Removal
331
+ with gr.Tab("✂️ Remove Background"):
332
+ with gr.Row():
333
+ with gr.Column(scale=1):
334
+ input_image = gr.Image(
335
+ label="📸 Input Image",
336
+ type="pil",
337
+ height=450
338
+ )
339
+
340
+ with gr.Row():
341
+ remove_btn = gr.Button(
342
+ "✂️ Remove Background",
343
+ variant="primary",
344
+ size="lg",
345
+ scale=2
346
+ )
347
+ clear_btn = gr.ClearButton(
348
+ components=[input_image],
349
+ value="🗑️ Clear",
350
+ size="lg",
351
+ scale=1
352
+ )
353
+
354
+ with gr.Column(scale=1):
355
+ output_image = gr.Image(
356
+ label="🖼️ Output (No Background)",
357
+ type="pil",
358
+ height=450
359
+ )
360
+
361
+ output_mask = gr.Image(
362
+ label="🎭 Alpha Mask",
363
+ type="pil",
364
+ height=200
365
+ )
366
+
367
+ output_json = gr.Code(
368
+ label="📄 JSON Response",
369
+ language="json",
370
+ lines=10,
371
+ elem_id="output_json"
372
+ )
373
+
374
+ gr.Markdown("""
375
+ ### 💡 Tips for Best Results
376
+ - Use images with **clear subject-background separation**
377
+ - **Good lighting** improves accuracy
378
+ - **Higher resolution** = better edge quality
379
+ - Images are automatically resized if too large (max 2048px)
380
+ - Save as **PNG** to preserve transparency
381
+ """)
382
+
383
+ remove_btn.click(
384
+ fn=remove_background,
385
+ inputs=[input_image],
386
+ outputs=[output_image, output_mask, output_json]
387
+ )
388
+
389
+ # Tab Model Info
390
+ with gr.Tab("ℹ️ Model Info"):
391
+ model_info_output = gr.Code(
392
+ label="Model Information & Statistics",
393
+ language="json",
394
+ lines=35
395
+ )
396
+ info_btn = gr.Button("🔍 Get Model Info & Stats", variant="secondary", size="lg")
397
+
398
+ gr.Markdown("""
399
+ ### About BiRefNet
400
+
401
+ **BiRefNet** (Bilateral Reference Network) is a state-of-the-art image segmentation model
402
+ specifically designed for high-quality background removal. It uses bilateral reference
403
+ mechanisms to achieve precise object segmentation with clean edges.
404
+
405
+ **Key Features:**
406
+ - Advanced bilateral architecture for precise segmentation
407
+ - Handles complex backgrounds and fine details
408
+ - Preserves hair, fur, and transparent objects
409
+ - Production-ready quality
410
+ """)
411
+
412
+ info_btn.click(
413
+ fn=get_model_info,
414
+ inputs=[],
415
+ outputs=model_info_output
416
+ )
417
+
418
+ # Tab API Documentation
419
+ with gr.Tab("📚 API Usage"):
420
+ gr.Markdown("""
421
+ ## 🚀 API Usage Guide
422
+
423
+ ### 1. Python Example with Requests
424
+ ```python
425
+ import requests
426
+ import base64
427
+ from PIL import Image
428
+ from io import BytesIO
429
+ import json
430
+
431
+ # Load and encode image
432
+ with open("input.jpg", "rb") as f:
433
+ img_data = base64.b64encode(f.read()).decode()
434
+
435
+ # API endpoint
436
+ url = "https://YOUR-SPACE-URL/api/predict"
437
+
438
+ payload = {
439
+ "data": [f"data:image/jpeg;base64,{img_data}"]
440
+ }
441
+
442
+ # Make request
443
+ response = requests.post(url, json=payload)
444
+ result = response.json()
445
+
446
+ # Get output image (PNG with transparency)
447
+ output_image_data = result['data'][0]
448
+ output_json = json.loads(result['data'][2])
449
+
450
+ # Decode and save
451
+ img_bytes = base64.b64decode(output_image_data.split(',')[1])
452
+ img = Image.open(BytesIO(img_bytes))
453
+ img.save('output_no_bg.png')
454
+
455
+ print(json.dumps(output_json, indent=2))
456
+ ```
457
+
458
+ ### 2. Using Gradio Client
459
+ ```python
460
+ from gradio_client import Client
461
+ from PIL import Image
462
+
463
+ client = Client("YOUR-SPACE-URL")
464
+
465
+ # Process image
466
+ result = client.predict(
467
+ input_image="path/to/image.jpg",
468
+ api_name="/predict"
469
+ )
470
+
471
+ # result contains: [output_image, mask, json_response]
472
+ output_path, mask_path, json_data = result
473
+
474
+ # Load and use
475
+ output = Image.open(output_path)
476
+ output.save("no_background.png")
477
+ ```
478
+
479
+ ### 3. Response Format
480
+ ```json
481
+ {
482
+ "success": true,
483
+ "input_size": "1200x1600",
484
+ "output_size": "1200x1600",
485
+ "output_format": "PNG with alpha channel",
486
+ "model": "mohantesting/remove_background",
487
+ "device": "cuda",
488
+ "processing_time": "~1-3 seconds"
489
+ }
490
+ ```
491
+
492
+ ### 4. Batch Processing Script
493
+ ```python
494
+ import os
495
+ from pathlib import Path
496
+ from gradio_client import Client
497
+ from PIL import Image
498
+
499
+ client = Client("YOUR-SPACE-URL")
500
+
501
+ input_dir = 'input_images'
502
+ output_dir = 'output_images'
503
+ os.makedirs(output_dir, exist_ok=True)
504
+
505
+ for img_file in Path(input_dir).glob('*.jpg'):
506
+ print(f"Processing: {img_file.name}")
507
+
508
+ result = client.predict(
509
+ input_image=str(img_file),
510
+ api_name="/predict"
511
+ )
512
+
513
+ output_path = result[0]
514
+ img = Image.open(output_path)
515
+
516
+ save_path = Path(output_dir) / f"{img_file.stem}_no_bg.png"
517
+ img.save(save_path)
518
+
519
+ print(f"✓ Saved: {save_path}")
520
+ ```
521
+
522
+ ### 5. Output Format Details
523
+
524
+ **Image Format:**
525
+ - Format: PNG with full alpha transparency
526
+ - Resolution: Same as input (up to 2048x2048)
527
+ - Background: Completely transparent (alpha = 0)
528
+ - Foreground: Fully preserved with smooth edges
529
+
530
+ **Alpha Mask:**
531
+ - Grayscale image showing segmentation confidence
532
+ - White (255) = foreground
533
+ - Black (0) = background
534
+ - Gray values = edge transitions
535
+
536
+ ### 6. Best Practices
537
+
538
+ ✅ **DO:**
539
+ - Use high-resolution images (1000px+ recommended)
540
+ - Ensure good contrast between subject and background
541
+ - Use well-lit, sharp images
542
+ - Save output as PNG to preserve transparency
543
+ - Test with sample images first
544
+
545
+ ❌ **DON'T:**
546
+ - Don't use extremely large images (>4K) - they'll be auto-resized
547
+ - Don't expect perfect results on very complex backgrounds
548
+ - Don't save as JPEG (loses transparency!)
549
+ - Don't use blurry or low-quality input images
550
+
551
+ ### 7. Common Use Cases
552
+
553
+ **E-Commerce Product Photos:**
554
+ ```python
555
+ # Remove background for clean product shots
556
+ result = remove_background('product.jpg')
557
+ result.save('product_transparent.png')
558
+ # Upload to Shopify, Amazon, etc.
559
+ ```
560
+
561
+ **Portrait Photography:**
562
+ ```python
563
+ # Create professional headshots
564
+ result = remove_background('portrait.jpg')
565
+ # Composite on professional backgrounds
566
+ ```
567
+
568
+ **Social Media Content:**
569
+ ```python
570
+ # Create stickers, cutouts, graphics
571
+ result = remove_background('subject.jpg')
572
+ # Use in Instagram, TikTok, YouTube thumbnails
573
+ ```
574
+
575
+ **Graphic Design:**
576
+ ```python
577
+ # Create design elements
578
+ result = remove_background('object.jpg')
579
+ # Import into Photoshop, Illustrator, Canva
580
+ ```
581
+
582
+ ### 8. Performance Metrics
583
+
584
+ - **Processing Time**: 1-3 seconds per image (GPU) / 5-10 seconds (CPU)
585
+ - **Max Resolution**: 2048x2048 (auto-resized if larger)
586
+ - **Model Size**: ~840MB
587
+ - **GPU Memory**: ~2GB recommended
588
+ - **Accuracy**: High-quality segmentation with clean edges
589
+
590
+ ### 9. Error Handling
591
+
592
+ ```python
593
+ try:
594
+ result = client.predict(input_image="image.jpg")
595
+ output_data = json.loads(result[2])
596
+
597
+ if output_data['success']:
598
+ print("Success!")
599
+ else:
600
+ print(f"Error: {output_data['error']}")
601
+
602
+ except Exception as e:
603
+ print(f"Request failed: {e}")
604
+ ```
605
+
606
+ ### 10. Rate Limits & Quotas
607
+
608
+ - No built-in rate limits (depends on hosting)
609
+ - For HuggingFace Spaces: Check your space tier
610
+ - For self-hosted: Limited by GPU/CPU resources
611
+ - Recommended: Process images sequentially for stability
612
+
613
+ ---
614
+
615
+ **Model:** mohantesting/remove_background (BiRefNet)
616
+ **Framework:** PyTorch + Transformers + Gradio
617
+ **License:** Check model repository for licensing details
618
+ """)
619
+
620
+ gr.Markdown("""
621
+ ---
622
+ <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;">
623
+ <h3 style="margin: 0; color: white;">🚀 Ready to integrate background removal into your app?</h3>
624
+ <p style="margin: 10px 0 0 0; opacity: 0.9;">Use the API documentation above to get started!</p>
625
+ </div>
626
+ """)
627
+
628
+ # Launch
629
+ if __name__ == "__main__":
630
+ demo.launch(
631
+ server_name="0.0.0.0",
632
+ server_port=7860,
633
+ share=False
634
  )