blanchon commited on
Commit
9d1a9fc
·
0 Parent(s):

{commit_message}

Browse files
.env.example ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ API_KEY=
2
+ DEPLOYMENT_ID=
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ examples/* filter=lfs diff=lfs merge=lfs -text
.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 ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Room Cleaner
3
+ emoji: 🧹
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 4.44.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # Room Cleaner
app.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import cast
2
+ from comfydeploy import ComfyDeploy
3
+ import asyncio
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 glob
14
+ import numpy as np
15
+
16
+ dotenv.load_dotenv()
17
+
18
+
19
+ API_KEY = os.environ.get("API_KEY")
20
+ DEPLOYMENT_ID = os.environ.get("DEPLOYMENT_ID", "DEPLOYMENT_ID_NOT_SET")
21
+
22
+ if not API_KEY:
23
+ raise ValueError(
24
+ "Please set API_KEY and DEPLOYMENT_ID in your environment variables"
25
+ )
26
+ if DEPLOYMENT_ID == "DEPLOYMENT_ID_NOT_SET":
27
+ raise ValueError("Please set DEPLOYMENT_ID in your environment variables")
28
+
29
+
30
+ client = ComfyDeploy(bearer_auth=API_KEY)
31
+
32
+
33
+ def get_base64_from_image(image: Image.Image) -> str:
34
+ buffered: BytesIO = BytesIO()
35
+ image.save(buffered, format="PNG")
36
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
37
+
38
+
39
+ async def process_image(
40
+ image: Image.Image | str | None,
41
+ mask: Image.Image | str | None,
42
+ progress: gr.Progress = gr.Progress(),
43
+ ) -> Image.Image | None:
44
+ progress(0, desc="Starting...")
45
+ if image is None or mask is None:
46
+ return None
47
+
48
+ if isinstance(mask, str):
49
+ mask = Image.open(mask)
50
+ if isinstance(image, str):
51
+ image = Image.open(image)
52
+
53
+ image_base64 = get_base64_from_image(image)
54
+ mask_base64 = get_base64_from_image(mask)
55
+
56
+ # Prepare inputs
57
+ inputs: dict = {
58
+ "image": f"data:image/png;base64,{image_base64}",
59
+ "mask": f"data:image/png;base64,{mask_base64}",
60
+ }
61
+
62
+ # Call ComfyDeploy API
63
+ try:
64
+ result = client.run.create(
65
+ request={"deployment_id": DEPLOYMENT_ID, "inputs": inputs}
66
+ )
67
+
68
+ if result and result.object:
69
+ run_id: str = result.object.run_id
70
+ progress(0, desc="Starting processing...")
71
+ # Wait for the result
72
+ while True:
73
+ run_result = client.run.get(run_id=run_id)
74
+ if not run_result.object:
75
+ continue
76
+
77
+ progress_value = (
78
+ run_result.object.progress
79
+ if run_result.object.progress is not None
80
+ else 0
81
+ )
82
+ status = (
83
+ run_result.object.live_status
84
+ if run_result.object.live_status is not None
85
+ else "Cold starting..."
86
+ )
87
+ progress(progress_value, desc=f"Status: {status}")
88
+
89
+ if run_result.object.status == "success":
90
+ for output in run_result.object.outputs or []:
91
+ if output.data and output.data.images:
92
+ image_url: str = output.data.images[0].url
93
+ # Download and return both the original and processed images
94
+ response: requests.Response = requests.get(image_url)
95
+ processed_image: Image.Image = Image.open(
96
+ BytesIO(response.content)
97
+ )
98
+ return processed_image
99
+ return None
100
+ elif run_result.object.status == "failed":
101
+ print("Processing failed")
102
+ return None
103
+
104
+ await asyncio.sleep(2) # Wait for 2 seconds before checking again
105
+ except Exception as e:
106
+ print(f"Error: {e}")
107
+ return None
108
+
109
+
110
+ def resize(image: Image.Image, shortest_side: int = 768) -> Image.Image:
111
+ if image.width <= shortest_side and image.height <= shortest_side:
112
+ return image
113
+ if image.width < image.height:
114
+ return image.resize(
115
+ size=(shortest_side, int(shortest_side * image.height / image.width))
116
+ )
117
+ return image.resize(
118
+ size=(int(shortest_side * image.width / image.height), shortest_side)
119
+ )
120
+
121
+
122
+ async def run_async(
123
+ image_and_mask: EditorValue | None,
124
+ progress: gr.Progress = gr.Progress(),
125
+ ) -> tuple[Image.Image, Image.Image] | None:
126
+ if not image_and_mask:
127
+ return None
128
+
129
+ alpha_channel = image_and_mask["layers"][0]
130
+ alpha_channel = cast(np.ndarray, alpha_channel)
131
+ mask_np = np.where(alpha_channel[:, :, 3] == 0, 0, 255).astype(np.uint8)
132
+
133
+ image_np = image_and_mask["background"]
134
+ image_np = cast(np.ndarray, image_np)
135
+
136
+ # Save mask to ./masks.png
137
+ mask = Image.fromarray(mask_np)
138
+ mask = resize(mask)
139
+ # mask.save("mask.png")
140
+
141
+ # Save image to ./images.png
142
+ image = Image.fromarray(image_np)
143
+ image = resize(image)
144
+ # image.save("image.png")
145
+
146
+ output = await process_image(
147
+ image, # type: ignore
148
+ mask, # type: ignore
149
+ progress,
150
+ )
151
+
152
+ if output is None:
153
+ return None
154
+
155
+ return output, image
156
+
157
+
158
+ def run_sync(*args):
159
+ return asyncio.run(run_async(*args))
160
+
161
+
162
+ with gr.Blocks() as demo:
163
+ gr.Markdown("# 🌊 ...")
164
+ with gr.Row():
165
+ with gr.Column():
166
+ # The image overflow, fix
167
+ image_and_mask = gr.ImageMask(
168
+ label="Input Image and Mask",
169
+ layers=False,
170
+ show_fullscreen_button=False,
171
+ sources=["upload"],
172
+ show_download_button=False,
173
+ interactive=True,
174
+ height="full",
175
+ width="full",
176
+ )
177
+
178
+ with gr.Column():
179
+ image_slider = ImageSlider(
180
+ label="Compare Original and Processed",
181
+ interactive=False,
182
+ )
183
+
184
+ process_btn = gr.ClearButton(
185
+ value="Run",
186
+ variant="primary",
187
+ size="lg",
188
+ components=[image_slider],
189
+ )
190
+ process_btn.click(
191
+ fn=run_sync,
192
+ inputs=[
193
+ image_and_mask,
194
+ ],
195
+ outputs=[image_slider],
196
+ api_name=False,
197
+ )
198
+
199
+ # Build examples
200
+ images_examples = glob.glob("examples/*")
201
+ mask_examples = [img.replace("inputs", "masks") for img in images_examples]
202
+ output_examples = [img.replace("inputs", "outputs") for img in images_examples]
203
+ # examples = [
204
+ # [
205
+ # img,
206
+ # mask,
207
+ # (img, out),
208
+ # ]
209
+ # for img, mask, out in zip(images_examples, mask_examples, output_examples)
210
+ # ]
211
+ examples = [
212
+ [
213
+ {
214
+ "background": "./examples/ex1.jpg",
215
+ "layers": [],
216
+ "composite": "./examples/ex1_mask.png",
217
+ },
218
+ ("./examples/ex1.jpg", "./examples/ex1_result.png"),
219
+ ],
220
+ [
221
+ {
222
+ "background": "./examples/ex2.jpg",
223
+ "layers": [],
224
+ "composite": "./examples/ex2_mask.png",
225
+ },
226
+ ("./examples/ex2.jpg", "./examples/ex2_result.png"),
227
+ ],
228
+ [
229
+ {
230
+ "background": "./examples/ex3.jpg",
231
+ "layers": [],
232
+ "composite": "./examples/ex3_mask.png",
233
+ },
234
+ ("./examples/ex3.jpg", "./examples/ex3_result.png"),
235
+ ],
236
+ [
237
+ {
238
+ "background": "./examples/ex4.jpg",
239
+ "layers": [],
240
+ "composite": "./examples/ex4_mask.png",
241
+ },
242
+ ("./examples/ex4.jpg", "./examples/ex4_result.png"),
243
+ ],
244
+ ]
245
+
246
+ # Update the gr.Examples call
247
+ gr.Examples(
248
+ examples=examples,
249
+ inputs=[
250
+ image_and_mask,
251
+ image_slider,
252
+ ],
253
+ api_name=False,
254
+ )
255
+
256
+ if __name__ == "__main__":
257
+ demo.launch(debug=True, share=True)
examples/ex1.jpg ADDED
examples/ex1_mask.png ADDED
examples/ex1_result.png ADDED
examples/ex2.jpg ADDED
examples/ex2_mask.png ADDED
examples/ex2_result.png ADDED
examples/ex3.jpg ADDED
examples/ex3_mask.png ADDED
examples/ex3_result.png ADDED
examples/ex4.jpg ADDED
examples/ex4_mask.png ADDED
examples/ex4_mask_only.png ADDED
examples/ex4_result.png ADDED
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ comfydeploy==0.3.4
2
+ gradio==4.44.0
3
+ gradio_imageslider==0.0.20
4
+ python-dotenv