IronJayx commited on
Commit
4bca48b
1 Parent(s): f60ad5d

base commit

Browse files
Files changed (4) hide show
  1. .gitignore +167 -0
  2. README.md +1 -0
  3. app.py +454 -0
  4. requirements.txt +6 -0
.gitignore ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ venv
7
+ flagged
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py,cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # poetry
101
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
105
+ #poetry.lock
106
+
107
+ # pdm
108
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
109
+ #pdm.lock
110
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
111
+ # in version control.
112
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
113
+ .pdm.toml
114
+ .pdm-python
115
+ .pdm-build/
116
+
117
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118
+ __pypackages__/
119
+
120
+ # Celery stuff
121
+ celerybeat-schedule
122
+ celerybeat.pid
123
+
124
+ # SageMath parsed files
125
+ *.sage.py
126
+
127
+ # Environments
128
+ .env
129
+ .venv
130
+ env/
131
+ venv/
132
+ ENV/
133
+ env.bak/
134
+ venv.bak/
135
+
136
+ # Spyder project settings
137
+ .spyderproject
138
+ .spyproject
139
+
140
+ # Rope project settings
141
+ .ropeproject
142
+
143
+ # mkdocs documentation
144
+ /site
145
+
146
+ # mypy
147
+ .mypy_cache/
148
+ .dmypy.json
149
+ dmypy.json
150
+
151
+ # Pyre type checker
152
+ .pyre/
153
+
154
+ # pytype static type analyzer
155
+ .pytype/
156
+
157
+ # Cython debug symbols
158
+ cython_debug/
159
+
160
+ # PyCharm
161
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
164
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165
+ #.idea/
166
+
167
+ .env
README.md CHANGED
@@ -7,6 +7,7 @@ sdk: gradio
7
  sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
7
  sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
+ hf_oauth: true
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from typing import cast
3
+ from comfydeploy import ComfyDeploy
4
+ import os
5
+ import gradio as gr
6
+ from gradio.components.image_editor import EditorValue
7
+ from PIL import Image
8
+ import requests
9
+ import dotenv
10
+ from gradio_imageslider import ImageSlider
11
+ from io import BytesIO
12
+ import base64
13
+ import numpy as np
14
+ from loguru import logger
15
+
16
+
17
+ dotenv.load_dotenv()
18
+
19
+
20
+ API_KEY = os.environ.get("API_KEY")
21
+ CLEANER_DEPLOYMENT_ID = os.environ.get(
22
+ "CLEANER_DEPLOYMENT_ID", "CLEANER_DEPLOYMENT_ID_NOT_SET"
23
+ )
24
+ MASKER_DEPLOYMENT_ID = os.environ.get(
25
+ "MASKER_DEPLOYMENT_ID", "MASKER_DEPLOYMENT_ID_NOT_SET"
26
+ )
27
+
28
+ if not API_KEY:
29
+ raise ValueError("Please set API_KEY in your environment variables")
30
+ if (
31
+ not CLEANER_DEPLOYMENT_ID
32
+ or CLEANER_DEPLOYMENT_ID == "CLEANER_DEPLOYMENT_ID_NOT_SET"
33
+ ):
34
+ raise ValueError("Please set CLEANER_DEPLOYMENT_ID in your environment variables")
35
+
36
+
37
+ client = ComfyDeploy(bearer_auth=API_KEY)
38
+
39
+
40
+ def get_base64_from_image(image: Image.Image) -> str:
41
+ buffered: BytesIO = BytesIO()
42
+ image.save(buffered, format="PNG")
43
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
44
+
45
+
46
+ def compute_mask(
47
+ image: Image.Image | str | None, progress: gr.Progress = gr.Progress()
48
+ ) -> Image.Image | None:
49
+ progress(0, desc="Preparing inputs...")
50
+ if image is None:
51
+ return None
52
+
53
+ image = resize_image(image)
54
+
55
+ image_base64 = get_base64_from_image(image)
56
+
57
+ # Prepare inputs
58
+ inputs: dict = {
59
+ "input_image": f"data:image/png;base64,{image_base64}",
60
+ "dilation_1_iterations": 20,
61
+ "dilation_2_iterations": 30,
62
+ "mask_blur_amount": 215,
63
+ }
64
+
65
+ # Call ComfyDeploy API
66
+ try:
67
+ result = client.run.create(
68
+ request={"deployment_id": MASKER_DEPLOYMENT_ID, "inputs": inputs}
69
+ )
70
+
71
+ if result and result.object:
72
+ run_id: str = result.object.run_id
73
+ progress(0, desc="Starting processing...")
74
+ # Wait for the result
75
+ while True:
76
+ run_result = client.run.get(run_id=run_id)
77
+ if not run_result.object:
78
+ continue
79
+
80
+ progress_value = run_result.object.progress or 0
81
+ status = run_result.object.live_status or "Cold starting..."
82
+ progress(progress_value, desc=f"Status: {status}")
83
+
84
+ if run_result.object.status == "success":
85
+ for output in run_result.object.outputs or []:
86
+ if output.data and output.data.images:
87
+ image_url: str = output.data.images[0].url
88
+ # Download and return the mask image
89
+ response: requests.Response = requests.get(image_url)
90
+ mask_image: Image.Image = Image.open(
91
+ BytesIO(response.content)
92
+ )
93
+ return mask_image
94
+ return None
95
+ elif run_result.object.status == "failed":
96
+ logger.debug("Processing failed")
97
+ return None
98
+
99
+ time.sleep(1) # Wait for 1 second before checking the status again
100
+ except Exception as e:
101
+ logger.debug(f"Error: {e}")
102
+ return None
103
+
104
+
105
+ def create_editor_value(image: Image.Image, mask: Image.Image) -> EditorValue:
106
+ # Convert image to numpy array
107
+ image_np = np.array(image)
108
+
109
+ # Resize mask to match image dimensions
110
+ mask_resized = mask.resize((image_np.shape[1], image_np.shape[0]), Image.NEAREST)
111
+ mask_np = np.array(mask_resized)
112
+
113
+ # Ensure mask is grayscale
114
+ if len(mask_np.shape) == 3:
115
+ mask_np = mask_np[:, :, -1]
116
+
117
+ # Create the layers array
118
+ layers = np.zeros((image_np.shape[0], image_np.shape[1], 4), dtype=np.uint8)
119
+ layers[:, :, 3] = mask_np
120
+
121
+ # Create the composite image
122
+ composite = np.zeros((image_np.shape[0], image_np.shape[1], 4), dtype=np.uint8)
123
+ composite[:, :, :3] = image_np
124
+ composite[:, :, 3] = np.where(mask_np == 255, 0, 255)
125
+
126
+ return {
127
+ "background": image_np,
128
+ "layers": [layers],
129
+ "composite": composite,
130
+ }
131
+
132
+
133
+ def run_masking(
134
+ image: np.ndarray | Image.Image | str | None,
135
+ progress: gr.Progress = gr.Progress(),
136
+ profile: gr.OAuthProfile | None = None,
137
+ ) -> EditorValue | None:
138
+ if image is None:
139
+ return None
140
+
141
+ if profile is None:
142
+ gr.Info("Please log in to process the image.")
143
+ return None
144
+
145
+ # Convert np.ndarray to Image.Image
146
+ if isinstance(image, np.ndarray):
147
+ image = Image.fromarray(image)
148
+ elif isinstance(image, str):
149
+ image = Image.open(image)
150
+
151
+ mask = compute_mask(image, progress)
152
+ if mask is None:
153
+ return None
154
+
155
+ # Use the new create_editor_value function
156
+ return create_editor_value(image, mask)
157
+
158
+
159
+ def remove_objects(
160
+ image: Image.Image | str | None,
161
+ mask: Image.Image | str | None,
162
+ user_data: dict,
163
+ progress: gr.Progress = gr.Progress(),
164
+ ) -> Image.Image | None:
165
+ progress(0, desc="Preparing inputs...")
166
+ if image is None or mask is None:
167
+ return None
168
+
169
+ if isinstance(mask, str):
170
+ mask = Image.open(mask)
171
+ if isinstance(image, str):
172
+ image = Image.open(image)
173
+
174
+ image_base64 = get_base64_from_image(image)
175
+ mask_base64 = get_base64_from_image(mask)
176
+
177
+ # Prepare inputs
178
+ inputs: dict = {
179
+ "image": f"data:image/png;base64,{image_base64}",
180
+ "mask": f"data:image/png;base64,{mask_base64}",
181
+ # "run_metatada": str(
182
+ # {
183
+ # "source": "HF",
184
+ # "user": user_data,
185
+ # }
186
+ # ),
187
+ }
188
+
189
+ # Call ComfyDeploy API
190
+ try:
191
+ result = client.run.create(
192
+ request={"deployment_id": CLEANER_DEPLOYMENT_ID, "inputs": inputs}
193
+ )
194
+
195
+ if result and result.object:
196
+ run_id: str = result.object.run_id
197
+ progress(0, desc="Starting processing...")
198
+ # Wait for the result
199
+ while True:
200
+ run_result = client.run.get(run_id=run_id)
201
+ if not run_result.object:
202
+ continue
203
+
204
+ progress_value = (
205
+ run_result.object.progress
206
+ if run_result.object.progress is not None
207
+ else 0
208
+ )
209
+ status = (
210
+ run_result.object.live_status
211
+ if run_result.object.live_status is not None
212
+ else "Cold starting..."
213
+ )
214
+ progress(progress_value, desc=f"Status: {status}")
215
+
216
+ if run_result.object.status == "success":
217
+ for output in run_result.object.outputs or []:
218
+ if output.data and output.data.images:
219
+ image_url: str = output.data.images[0].url
220
+ # Download and return both the original and processed images
221
+ response: requests.Response = requests.get(image_url)
222
+ processed_image: Image.Image = Image.open(
223
+ BytesIO(response.content)
224
+ )
225
+ return processed_image
226
+ return None
227
+ elif run_result.object.status == "failed":
228
+ logger.debug("Processing failed")
229
+ return None
230
+
231
+ time.sleep(1) # Wait for 1 second before checking the status again
232
+ except Exception as e:
233
+ logger.debug(f"Error: {e}")
234
+ return None
235
+
236
+
237
+ def resize_image(img: Image.Image, min_side_length: int = 768) -> Image.Image:
238
+ if img.width <= min_side_length and img.height <= min_side_length:
239
+ return img
240
+
241
+ aspect_ratio = img.width / img.height
242
+ if img.width < img.height:
243
+ new_height = int(min_side_length / aspect_ratio)
244
+ return img.resize((min_side_length, new_height))
245
+
246
+ new_width = int(min_side_length * aspect_ratio)
247
+ return img.resize((new_width, min_side_length))
248
+
249
+
250
+ def get_profile(profile) -> dict:
251
+ return {
252
+ "username": profile.username,
253
+ "profile": profile.profile,
254
+ "name": profile.name,
255
+ }
256
+
257
+
258
+ async def run_removal(
259
+ image_and_mask: EditorValue | None,
260
+ progress: gr.Progress = gr.Progress(),
261
+ profile: gr.OAuthProfile | None = None,
262
+ ) -> tuple[Image.Image, Image.Image] | None:
263
+ if not image_and_mask:
264
+ gr.Info("Please upload an image and draw a mask")
265
+ return None
266
+
267
+ if profile is None:
268
+ gr.Info("Please log in to process the image.")
269
+ return None
270
+
271
+ user_data = get_profile(profile)
272
+ logger.debug("--------- RUN ----------")
273
+ logger.debug(user_data)
274
+ logger.debug("--------- RUN ----------")
275
+
276
+ image_np = image_and_mask["background"]
277
+ image_np = cast(np.ndarray, image_np)
278
+
279
+ # If the image is empty, return None
280
+ if np.sum(image_np) == 0:
281
+ gr.Info("Please upload an image")
282
+ return None
283
+
284
+ alpha_channel = image_and_mask["layers"][0]
285
+ alpha_channel = cast(np.ndarray, alpha_channel)
286
+ mask_np = np.where(alpha_channel[:, :, 3] == 0, 0, 255).astype(np.uint8)
287
+
288
+ # if mask_np is empty, return None
289
+ if np.sum(mask_np) == 0:
290
+ gr.Info("Please mark the areas you want to remove")
291
+ return None
292
+
293
+ mask = Image.fromarray(mask_np)
294
+ mask = resize_image(mask)
295
+
296
+ image = Image.fromarray(image_np)
297
+ image = resize_image(image)
298
+
299
+ output = remove_objects(
300
+ image, # type: ignore
301
+ mask, # type: ignore
302
+ user_data,
303
+ progress,
304
+ )
305
+
306
+ if output is None:
307
+ gr.Info("Processing failed")
308
+ return None
309
+ progress(100, desc="Processing completed")
310
+ return image, output
311
+
312
+
313
+ with gr.Blocks() as demo:
314
+ gr.HTML("""
315
+ <div style="display: flex; justify-content: center; text-align:center; flex-direction: column;">
316
+ <h1 style="color: #333;">🧹 Room Cleaner</h1>
317
+ <div style="max-width: 800px; margin: 0 auto;">
318
+ <p style="font-size: 16px;">Upload an image and use the pencil tool (✏️ icon at the bottom) to <b>mark the areas you want to remove</b>.</p>
319
+ <p style="font-size: 16px;">
320
+ For best results, include the shadows and reflections of the objects you want to remove.
321
+ You can remove multiple objects at once.
322
+ If you forget to mask some parts of your object, it's likely that the model will reconstruct them.
323
+ </p>
324
+ <br>
325
+ <video width="640" height="360" controls style="margin: 0 auto; border-radius: 10px;">
326
+ <source src="https://dropshare.blanchon.xyz/public/dropshare/room_cleaner_demo.mp4" type="video/mp4">
327
+ </video>
328
+ <br>
329
+ <p style="font-size: 16px;">Finally, click on the <b>"Run"</b> button to process the image.</p>
330
+ <p style="font-size: 16px;">Wait for the processing to complete and compare the original and processed images using the slider.</p>
331
+
332
+ <p style="font-size: 16px;">⚠️ Note that the images are compressed to reduce the workloads of the demo. </p>
333
+ </div>
334
+ <div style="margin-top: 20px; display: flex; justify-content: center; gap: 10px;">
335
+ <a href="https://x.com/JulienBlanchon">
336
+ <img src="https://img.shields.io/badge/X-%23000000.svg?style=for-the-badge&logo=X&logoColor=white" alt="X Badge" style="border-radius: 3px;"/>
337
+ </a>
338
+ <a href="https://x.com/jeremie_feron">
339
+ <img src="https://img.shields.io/badge/X-%23000000.svg?style=for-the-badge&logo=X&logoColor=white" alt="X Badge" style="border-radius: 3px;"/>
340
+ </a>
341
+ </div>
342
+ </div>
343
+ """)
344
+
345
+ login_button = gr.LoginButton(scale=8)
346
+
347
+ # ------ MASKING
348
+
349
+ with gr.Column():
350
+ with gr.Row(equal_height=False):
351
+ # The image overflow, fix
352
+ input_image = gr.Image(
353
+ label="Input Image",
354
+ height="full",
355
+ width="full",
356
+ )
357
+
358
+ gr.HTML("""
359
+ <h3 style="text-align: center;">Step 1: input image</h3>
360
+ <p style="text-align: center;">Upload an image of the room you want to clean.</p>
361
+ """)
362
+
363
+ with gr.Row(equal_height=False):
364
+ image_and_mask_auto = gr.ImageMask(
365
+ label="Image and Mask",
366
+ layers=False,
367
+ show_fullscreen_button=False,
368
+ sources=["upload"],
369
+ show_download_button=False,
370
+ interactive=True,
371
+ height="full",
372
+ width="full",
373
+ brush=gr.Brush(default_size=75, colors=["#000000"], color_mode="fixed"),
374
+ transforms=[],
375
+ )
376
+
377
+ with gr.Column():
378
+ gr.HTML("""
379
+ <h3 style="text-align: center;">Step 2: Run masking</h3>
380
+ <p style="text-align: center;">Click get mask to get automatic masking and edit it after manually if needed.</p>
381
+ """)
382
+ compute_mask_btn = gr.ClearButton(
383
+ value="Get mask",
384
+ variant="primary",
385
+ size="lg",
386
+ components=[image_and_mask_auto],
387
+ )
388
+
389
+ compute_mask_btn.click(
390
+ fn=lambda _: gr.update(interactive=False, value="Processing..."),
391
+ inputs=[],
392
+ outputs=[compute_mask_btn],
393
+ api_name=False,
394
+ ).then(
395
+ fn=run_masking,
396
+ inputs=[
397
+ input_image,
398
+ ],
399
+ outputs=[image_and_mask_auto],
400
+ api_name=False,
401
+ ).then(
402
+ fn=lambda _: gr.update(interactive=True, value="Get mask"),
403
+ inputs=[],
404
+ outputs=[compute_mask_btn],
405
+ api_name=False,
406
+ )
407
+
408
+ # ------ REMOVAL
409
+
410
+ with gr.Row(equal_height=False):
411
+ image_slider = ImageSlider(
412
+ label="Result",
413
+ interactive=False,
414
+ )
415
+
416
+ with gr.Column():
417
+ gr.HTML("""
418
+ <h3 style="text-align: center;">Step 3: Run removal</h3>
419
+ <p style="text-align: center;">Click run to remove the objects from the image.</p>
420
+ """)
421
+
422
+ process_btn = gr.ClearButton(
423
+ value="Run",
424
+ variant="primary",
425
+ size="lg",
426
+ components=[image_slider],
427
+ )
428
+
429
+ process_btn.click(
430
+ fn=lambda _: gr.update(interactive=False, value="Processing..."),
431
+ inputs=[],
432
+ outputs=[process_btn],
433
+ api_name=False,
434
+ ).then(
435
+ fn=run_removal,
436
+ inputs=[
437
+ image_and_mask_auto,
438
+ ],
439
+ outputs=[image_slider],
440
+ api_name=False,
441
+ ).then(
442
+ fn=lambda _: gr.update(interactive=True, value="Run"),
443
+ inputs=[],
444
+ outputs=[process_btn],
445
+ api_name=False,
446
+ )
447
+
448
+
449
+ if __name__ == "__main__":
450
+ demo.launch(
451
+ debug=False,
452
+ share=False,
453
+ show_api=False,
454
+ )
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ comfydeploy==0.3.4
2
+ gradio==4.44.0
3
+ gradio_imageslider==0.0.20
4
+ python-dotenv
5
+ gradio[oauth]
6
+ loguru