pszemraj commited on
Commit
8a0efd9
·
verified ·
1 Parent(s): b9afe6d

Claude remodel

Browse files
Files changed (3) hide show
  1. README.md +8 -1
  2. app.py +230 -37
  3. requirements.txt +2 -0
README.md CHANGED
@@ -13,6 +13,13 @@ preload_from_hub:
13
 
14
  # BEN2 Background Remover
15
 
16
- Upload an image and download a transparent PNG. Edge refinement is optional.
 
 
 
17
 
18
  Select **ZeroGPU** in the Space hardware settings after creating the Space.
 
 
 
 
 
13
 
14
  # BEN2 Background Remover
15
 
16
+ Upload, paste, or capture an image and get a clean cut-out. Compare the result
17
+ against the original with a before/after slider, optionally refine hair and fine
18
+ edges, and either keep the background transparent or drop the subject onto a
19
+ solid color. Download the result as a PNG.
20
 
21
  Select **ZeroGPU** in the Space hardware settings after creating the Space.
22
+
23
+ To add one-click examples, create an `examples/` folder next to `app.py` and
24
+ drop image files (`.png`, `.jpg`, `.jpeg`, `.webp`, `.bmp`) into it — they'll
25
+ appear automatically. No folder means no examples, and nothing breaks.
app.py CHANGED
@@ -3,12 +3,20 @@ from __future__ import annotations
3
  # ZeroGPU must patch PyTorch before anything else imports it.
4
  import spaces
5
 
 
 
 
6
  import gradio as gr
 
7
  import torch
8
  from ben2 import BEN_Base
9
  from PIL import Image, ImageOps
10
 
11
  MODEL_ID = "PramaLLC/BEN2"
 
 
 
 
12
  MAX_IMAGE_PIXELS = 40_000_000
13
  MAX_REFINEMENT_PIXELS = 12_000_000
14
 
@@ -17,7 +25,81 @@ model.to("cuda").eval()
17
  model.requires_grad_(False)
18
 
19
 
20
- def gpu_duration(_image: Image.Image | None, refine_foreground: bool) -> int:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  return 45 if refine_foreground else 30
22
 
23
 
@@ -25,7 +107,9 @@ def gpu_duration(_image: Image.Image | None, refine_foreground: bool) -> int:
25
  def remove_background(
26
  image: Image.Image | None,
27
  refine_foreground: bool,
28
- ) -> Image.Image:
 
 
29
  if image is None:
30
  raise gr.Error("Upload, paste, or capture an image first.")
31
 
@@ -45,46 +129,155 @@ def remove_background(
45
  image.copy(),
46
  refine_foreground=bool(refine_foreground),
47
  )
 
 
 
48
 
49
- return foreground.convert("RGBA")
50
-
51
-
52
- demo = gr.Interface(
53
- fn=remove_background,
54
- inputs=[
55
- gr.Image(
56
- type="pil",
57
- image_mode="RGB",
58
- sources=["upload", "clipboard", "webcam"],
59
- label="Input image",
60
- ),
61
- gr.Checkbox(
62
- value=False,
63
- label="Refine edges",
64
- info="Slower, but sometimes improves hair and fine boundaries.",
65
- ),
66
- ],
67
- outputs=gr.Image(
68
- type="pil",
69
- image_mode="RGBA",
70
- format="png",
71
- label="Transparent PNG",
72
- buttons=["download", "fullscreen"],
73
- ),
74
- title="BEN2 Background Remover",
75
- description="Upload an image, remove its background, and download the transparent PNG.",
76
- submit_btn="Remove background",
77
- clear_btn="Clear",
78
- flagging_mode="never",
79
- api_name="remove_background",
80
- concurrency_limit=1,
81
- time_limit=60,
82
- fill_width=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  )
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  if __name__ == "__main__":
86
  demo.queue(
87
  max_size=20,
88
  api_open=False,
89
  default_concurrency_limit=1,
90
- ).launch()
 
3
  # ZeroGPU must patch PyTorch before anything else imports it.
4
  import spaces
5
 
6
+ import os
7
+ import tempfile
8
+
9
  import gradio as gr
10
+ import numpy as np
11
  import torch
12
  from ben2 import BEN_Base
13
  from PIL import Image, ImageOps
14
 
15
  MODEL_ID = "PramaLLC/BEN2"
16
+ MODEL_URL = "https://huggingface.co/PramaLLC/BEN2"
17
+
18
+ # Guard rails: the model happily eats memory on huge inputs, and edge
19
+ # refinement is far more expensive, so it gets a tighter ceiling.
20
  MAX_IMAGE_PIXELS = 40_000_000
21
  MAX_REFINEMENT_PIXELS = 12_000_000
22
 
 
25
  model.requires_grad_(False)
26
 
27
 
28
+ # --------------------------------------------------------------------------- #
29
+ # Background options
30
+ # --------------------------------------------------------------------------- #
31
+ BG_TRANSPARENT = "Transparent"
32
+ BG_WHITE = "White"
33
+ BG_BLACK = "Black"
34
+ BG_CUSTOM = "Custom color"
35
+ BG_CHOICES = [BG_TRANSPARENT, BG_WHITE, BG_BLACK, BG_CUSTOM]
36
+
37
+ _SOLID_RGB = {BG_WHITE: (255, 255, 255), BG_BLACK: (0, 0, 0)}
38
+ _DEFAULT_RGB = (255, 255, 255)
39
+
40
+
41
+ def _parse_color(value: str | None) -> tuple[int, int, int]:
42
+ """Turn a ColorPicker value ('#rrggbb', '#rgb', 'rgb(...)', 'rgba(...)') into RGB."""
43
+ if not value:
44
+ return _DEFAULT_RGB
45
+ v = value.strip()
46
+ if v.startswith("#"):
47
+ v = v[1:]
48
+ if len(v) == 3:
49
+ v = "".join(c * 2 for c in v)
50
+ try:
51
+ return int(v[0:2], 16), int(v[2:4], 16), int(v[4:6], 16)
52
+ except (ValueError, IndexError):
53
+ return _DEFAULT_RGB
54
+ if v.lower().startswith(("rgb(", "rgba(")):
55
+ try:
56
+ parts = v[v.index("(") + 1 : v.index(")")].split(",")
57
+ rgb = (int(round(float(p))) for p in parts[:3])
58
+ r, g, b = (max(0, min(255, n)) for n in rgb)
59
+ return r, g, b
60
+ except (ValueError, IndexError):
61
+ return _DEFAULT_RGB
62
+ return _DEFAULT_RGB
63
+
64
+
65
+ def _checkerboard(size: tuple[int, int], square: int = 16) -> Image.Image:
66
+ """A transparency checkerboard sized to the image, so cut-outs read clearly."""
67
+ w, h = size
68
+ rows = (np.arange(h) // square)[:, None]
69
+ cols = (np.arange(w) // square)[None, :]
70
+ dark = ((rows + cols) & 1).astype(bool)
71
+ board = np.empty((h, w, 3), dtype=np.uint8)
72
+ board[~dark] = (245, 245, 245)
73
+ board[dark] = (208, 208, 208)
74
+ return Image.fromarray(board, mode="RGB")
75
+
76
+
77
+ def _flatten(cutout: Image.Image, background: Image.Image) -> Image.Image:
78
+ """Alpha-composite an RGBA cut-out over an opaque RGB background."""
79
+ flat = background.convert("RGB").copy()
80
+ flat.paste(cutout, mask=cutout.split()[-1])
81
+ return flat
82
+
83
+
84
+ def _compose(cutout: Image.Image, bg_choice: str, custom_color: str | None):
85
+ """
86
+ From the RGBA cut-out, build both:
87
+ - download: exactly what the user asked for (transparent RGBA, or flattened RGB)
88
+ - preview: an always-visible RGB image for the before/after slider
89
+ """
90
+ if bg_choice == BG_TRANSPARENT:
91
+ preview = _flatten(cutout, _checkerboard(cutout.size))
92
+ return cutout, preview # RGBA download, checkerboard preview
93
+
94
+ color = _SOLID_RGB.get(bg_choice) or _parse_color(custom_color)
95
+ flat = _flatten(cutout, Image.new("RGB", cutout.size, color))
96
+ return flat, flat # RGB download == preview
97
+
98
+
99
+ # --------------------------------------------------------------------------- #
100
+ # Inference
101
+ # --------------------------------------------------------------------------- #
102
+ def gpu_duration(image, refine_foreground, bg_choice, custom_color) -> int:
103
  return 45 if refine_foreground else 30
104
 
105
 
 
107
  def remove_background(
108
  image: Image.Image | None,
109
  refine_foreground: bool,
110
+ bg_choice: str,
111
+ custom_color: str | None,
112
+ ):
113
  if image is None:
114
  raise gr.Error("Upload, paste, or capture an image first.")
115
 
 
129
  image.copy(),
130
  refine_foreground=bool(refine_foreground),
131
  )
132
+ cutout = foreground.convert("RGBA")
133
+
134
+ download_img, preview = _compose(cutout, bg_choice, custom_color)
135
 
136
+ out_path = os.path.join(tempfile.mkdtemp(), "background-removed.png")
137
+ download_img.save(out_path)
138
+
139
+ return (image, preview), gr.DownloadButton(value=out_path, visible=True)
140
+
141
+
142
+ # --------------------------------------------------------------------------- #
143
+ # UI
144
+ # --------------------------------------------------------------------------- #
145
+ CSS = """
146
+ #app-wrap { max-width: 1140px; margin: 0 auto; }
147
+ #app-header { text-align: center; padding: 10px 0 2px; }
148
+ #app-header h1 {
149
+ margin: 0; font-size: 1.9rem; font-weight: 700; letter-spacing: -0.02em;
150
+ }
151
+ #app-header p {
152
+ margin: 6px auto 0; max-width: 640px;
153
+ color: var(--body-text-color-subdued); font-size: 0.98rem; line-height: 1.5;
154
+ }
155
+ #app-header a { color: var(--link-text-color); text-decoration: none; }
156
+ #app-header a:hover { text-decoration: underline; }
157
+ /* soft transparency checkerboard behind the result, visible in any letterboxing */
158
+ .checker-bg .image-frame, .checker-bg .empty {
159
+ background-color: #fbfbfb;
160
+ background-image:
161
+ linear-gradient(45deg, #e6e6e6 25%, transparent 25%),
162
+ linear-gradient(-45deg, #e6e6e6 25%, transparent 25%),
163
+ linear-gradient(45deg, transparent 75%, #e6e6e6 75%),
164
+ linear-gradient(-45deg, transparent 75%, #e6e6e6 75%);
165
+ background-size: 22px 22px;
166
+ background-position: 0 0, 0 11px, 11px -11px, -11px 0;
167
+ }
168
+ """
169
+
170
+ HEADER = f"""
171
+ <div id="app-header">
172
+ <h1>BEN2 Background Remover</h1>
173
+ <p>Cut the background out of any photo and download a clean transparent PNG —
174
+ or drop your subject onto a solid color. Powered by
175
+ <a href="{MODEL_URL}" target="_blank" rel="noopener">BEN2</a>.</p>
176
+ </div>
177
+ """
178
+
179
+ # Optional: drop image files into an `examples/` folder next to this file and
180
+ # they'll appear as one-click examples. No folder -> no examples, no error.
181
+ _EXT = (".png", ".jpg", ".jpeg", ".webp", ".bmp")
182
+ _EX_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "examples")
183
+ EXAMPLE_FILES = (
184
+ sorted(
185
+ os.path.join(_EX_DIR, f)
186
+ for f in os.listdir(_EX_DIR)
187
+ if f.lower().endswith(_EXT)
188
+ )
189
+ if os.path.isdir(_EX_DIR)
190
+ else []
191
  )
192
 
193
+
194
+ with gr.Blocks(title="BEN2 Background Remover", fill_width=True) as demo:
195
+ with gr.Column(elem_id="app-wrap"):
196
+ gr.HTML(HEADER)
197
+
198
+ with gr.Row(equal_height=False):
199
+ with gr.Column(scale=1):
200
+ input_image = gr.Image(
201
+ type="pil",
202
+ image_mode="RGB",
203
+ sources=["upload", "clipboard", "webcam"],
204
+ label="Input",
205
+ placeholder="Drop an image here, paste from the clipboard, "
206
+ "or capture from your webcam",
207
+ height=400,
208
+ )
209
+ with gr.Group():
210
+ refine = gr.Checkbox(
211
+ value=False,
212
+ label="Refine edges",
213
+ info="Slower, but recovers hair and fine boundaries.",
214
+ )
215
+ bg_choice = gr.Radio(
216
+ BG_CHOICES,
217
+ value=BG_TRANSPARENT,
218
+ label="Background",
219
+ info="Keep it transparent, or fill it with a color.",
220
+ )
221
+ custom_color = gr.ColorPicker(
222
+ value="#00b140", # chroma-key green
223
+ label="Custom color",
224
+ visible=False,
225
+ )
226
+ with gr.Row():
227
+ clear_btn = gr.ClearButton(value="Clear")
228
+ run_btn = gr.Button("Remove background", variant="primary")
229
+
230
+ with gr.Column(scale=1):
231
+ output_slider = gr.ImageSlider(
232
+ label="Before / after",
233
+ type="pil",
234
+ image_mode="RGB",
235
+ slider_position=50,
236
+ buttons=["fullscreen"],
237
+ interactive=False,
238
+ height=400,
239
+ elem_classes=["checker-bg"],
240
+ )
241
+ download_btn = gr.DownloadButton(
242
+ "Download PNG",
243
+ variant="primary",
244
+ visible=False,
245
+ )
246
+
247
+ if EXAMPLE_FILES:
248
+ gr.Examples(
249
+ examples=[[f] for f in EXAMPLE_FILES],
250
+ inputs=[input_image],
251
+ label="Try an example",
252
+ )
253
+
254
+ # Reveal the color picker only when a custom color is selected.
255
+ bg_choice.change(
256
+ lambda choice: gr.ColorPicker(visible=choice == BG_CUSTOM),
257
+ inputs=bg_choice,
258
+ outputs=custom_color,
259
+ show_progress="hidden",
260
+ )
261
+
262
+ clear_btn.add([input_image, output_slider])
263
+ clear_btn.click(
264
+ lambda: gr.DownloadButton(visible=False),
265
+ outputs=download_btn,
266
+ show_progress="hidden",
267
+ )
268
+
269
+ run_btn.click(
270
+ remove_background,
271
+ inputs=[input_image, refine, bg_choice, custom_color],
272
+ outputs=[output_slider, download_btn],
273
+ api_name="remove_background",
274
+ concurrency_limit=1,
275
+ )
276
+
277
+
278
  if __name__ == "__main__":
279
  demo.queue(
280
  max_size=20,
281
  api_open=False,
282
  default_concurrency_limit=1,
283
+ ).launch(theme=gr.themes.Ocean(), css=CSS)
requirements.txt CHANGED
@@ -1,5 +1,7 @@
1
  torch==2.11.0
2
  torchvision==0.26.0
 
 
3
  opencv-python-headless
4
  safetensors
5
  git+https://github.com/PramaLLC/BEN2.git
 
1
  torch==2.11.0
2
  torchvision==0.26.0
3
+ numpy
4
+ pillow
5
  opencv-python-headless
6
  safetensors
7
  git+https://github.com/PramaLLC/BEN2.git