blanchon commited on
Commit
a134dbf
·
1 Parent(s): 65ddab1
Files changed (3) hide show
  1. app.py +294 -0
  2. pyproject.toml +13 -0
  3. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import io
3
+ import os
4
+ import zipfile
5
+ from io import BytesIO
6
+ from typing import Literal, cast
7
+
8
+ import gradio as gr
9
+ import numpy as np
10
+ import requests
11
+ from gradio.components.image_editor import EditorValue
12
+ from PIL import Image
13
+
14
+ PASSWORD = os.environ.get("PASSWORD", None)
15
+ if not PASSWORD:
16
+ raise ValueError("PASSWORD is not set")
17
+
18
+ ENDPOINT = os.environ.get("ENDPOINT", None)
19
+ if not ENDPOINT:
20
+ raise ValueError("ENDPOINT is not set")
21
+
22
+
23
+ def encode_image_as_base64(image: Image.Image) -> str:
24
+ buffered = BytesIO()
25
+ image.save(buffered, format="PNG")
26
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
27
+
28
+
29
+ def predict(
30
+ model_type: Literal["schnell", "dev"],
31
+ image_and_mask: EditorValue,
32
+ furniture_reference: Image.Image | None,
33
+ prompt: str = "",
34
+ subfolder: str = "",
35
+ seed: int = 0,
36
+ num_inference_steps: int = 28,
37
+ max_dimension: int = 512,
38
+ margin: int = 64,
39
+ crop: bool = True,
40
+ num_images_per_prompt: int = 1,
41
+ ) -> list[Image.Image] | None:
42
+ if not image_and_mask:
43
+ gr.Info("Please upload an image and draw a mask")
44
+ return None
45
+ if not furniture_reference:
46
+ gr.Info("Please upload a furniture reference image")
47
+ return None
48
+
49
+ image_np = image_and_mask["background"]
50
+ image_np = cast(np.ndarray, image_np)
51
+
52
+ # If the image is empty, return None
53
+ if np.sum(image_np) == 0:
54
+ gr.Info("Please upload an image")
55
+ return None
56
+
57
+ alpha_channel = image_and_mask["layers"][0]
58
+ alpha_channel = cast(np.ndarray, alpha_channel)
59
+ mask_np = np.where(alpha_channel[:, :, 3] == 0, 0, 255).astype(np.uint8)
60
+
61
+ # if mask_np is empty, return None
62
+ if np.sum(mask_np) == 0:
63
+ gr.Info("Please mark the areas you want to remove")
64
+ return None
65
+
66
+ mask_image = Image.fromarray(mask_np).convert("L")
67
+ target_image = Image.fromarray(image_np).convert("RGB")
68
+
69
+ # Avoid too big image to be sent to the API
70
+ mask_image.thumbnail((2048, 2048), Image.Resampling.LANCZOS)
71
+ target_image.thumbnail((2048, 2048), Image.Resampling.LANCZOS)
72
+ furniture_reference.thumbnail((1024, 1024), Image.Resampling.LANCZOS)
73
+
74
+ room_image_input_base64 = encode_image_as_base64(target_image)
75
+ room_image_mask_base64 = encode_image_as_base64(mask_image)
76
+ furniture_reference_base64 = encode_image_as_base64(furniture_reference)
77
+
78
+ room_image_input_base64 = "data:image/png;base64," + room_image_input_base64
79
+ room_image_mask_base64 = "data:image/png;base64," + room_image_mask_base64
80
+ furniture_reference_base64 = "data:image/png;base64," + furniture_reference_base64
81
+
82
+ response = requests.post(
83
+ ENDPOINT,
84
+ headers={"accept": "application/json", "Content-Type": "application/json"},
85
+ json={
86
+ "model_type": model_type,
87
+ "room_image_input": room_image_input_base64,
88
+ "room_image_mask": room_image_mask_base64,
89
+ "furniture_reference_image": furniture_reference_base64,
90
+ "prompt": prompt,
91
+ "subfolder": subfolder,
92
+ "seed": seed,
93
+ "num_inference_steps": num_inference_steps,
94
+ "max_dimension": max_dimension,
95
+ "condition_scale": 1.0,
96
+ "margin": margin,
97
+ "crop": crop,
98
+ "num_images_per_prompt": num_images_per_prompt,
99
+ "password": PASSWORD,
100
+ },
101
+ )
102
+ if response.status_code != 200:
103
+ gr.Info("An error occurred during the generation")
104
+ return None
105
+
106
+ # Read the returned ZIP file from the response.
107
+ zip_bytes = io.BytesIO(response.content)
108
+
109
+ final_image_list: list[Image.Image] = []
110
+
111
+ # Open the ZIP archive.
112
+ with zipfile.ZipFile(zip_bytes, "r") as zip_file:
113
+ image_filenames = zip_file.namelist()
114
+ for filename in image_filenames:
115
+ with zip_file.open(filename) as file:
116
+ image = Image.open(file).convert("RGB")
117
+ final_image_list.append(image)
118
+
119
+ return final_image_list
120
+
121
+
122
+ intro_markdown = r"""
123
+ # Furniture Blending Demo
124
+ """
125
+
126
+ css = r"""
127
+ #col-left {
128
+ margin: 0 auto;
129
+ max-width: 430px;
130
+ }
131
+ #col-mid {
132
+ margin: 0 auto;
133
+ max-width: 430px;
134
+ }
135
+ #col-right {
136
+ margin: 0 auto;
137
+ max-width: 430px;
138
+ }
139
+ #col-showcase {
140
+ margin: 0 auto;
141
+ max-width: 1100px;
142
+ }
143
+ """
144
+
145
+
146
+ with gr.Blocks(css=css) as demo:
147
+ gr.Markdown(intro_markdown)
148
+
149
+ with gr.Row() as content:
150
+ with gr.Column(elem_id="col-left"):
151
+ gr.HTML(
152
+ r"""
153
+ <div style="display: flex; justify-content: start; align-items: center; text-align: center; font-size: 20px; height: 50px;">
154
+ <div>
155
+ 🪟 Room image with inpainting mask ⬇️
156
+ </div>
157
+ </div>
158
+ """,
159
+ max_height=50,
160
+ )
161
+ image_and_mask = gr.ImageMask(
162
+ label="Image and Mask",
163
+ layers=False,
164
+ height="full",
165
+ width="full",
166
+ show_fullscreen_button=False,
167
+ sources=["upload"],
168
+ show_download_button=False,
169
+ interactive=True,
170
+ brush=gr.Brush(default_size=75, colors=["#000000"], color_mode="fixed"),
171
+ transforms=[],
172
+ )
173
+ with gr.Column(elem_id="col-mid"):
174
+ gr.HTML(
175
+ r"""
176
+ <div style="display: flex; justify-content: start; align-items: center; text-align: center; font-size: 20px; height: 50px;">
177
+ <div>
178
+ 🪑 Furniture reference image ⬇️
179
+ </div>
180
+ </div>
181
+ """,
182
+ max_height=50,
183
+ )
184
+ condition_image = gr.Image(
185
+ label="Furniture Reference",
186
+ type="pil",
187
+ sources=["upload"],
188
+ image_mode="RGB",
189
+ )
190
+ with gr.Column(elem_id="col-right"):
191
+ gr.HTML(
192
+ r"""
193
+ <div style="display: flex; justify-content: start; align-items: center; text-align: center; font-size: 20px; height: 50px;">
194
+ <div>
195
+ 🔥 Press Run ⬇️
196
+ </div>
197
+ </div>
198
+ """,
199
+ max_height=50,
200
+ )
201
+ results = gr.Gallery(
202
+ label="Result",
203
+ format="png",
204
+ file_types="image",
205
+ show_label=False,
206
+ columns=2,
207
+ allow_preview=True,
208
+ preview=True,
209
+ )
210
+ model_type = gr.Radio(
211
+ choices=["schnell", "dev"],
212
+ value="schnell",
213
+ label="Model Type",
214
+ )
215
+ run_button = gr.Button("Run")
216
+
217
+ with gr.Accordion("Advanced Settings", open=False):
218
+ prompt = gr.Textbox(
219
+ label="Prompt",
220
+ value="",
221
+ )
222
+ subfolder = gr.Textbox(
223
+ label="Subfolder",
224
+ value="",
225
+ )
226
+ seed = gr.Slider(
227
+ label="Seed",
228
+ minimum=0,
229
+ maximum=np.iinfo(np.int32).max,
230
+ step=1,
231
+ value=0,
232
+ )
233
+ num_images_per_prompt = gr.Slider(
234
+ label="Number of images per prompt",
235
+ minimum=1,
236
+ maximum=10,
237
+ step=1,
238
+ value=4,
239
+ )
240
+ crop = gr.Checkbox(
241
+ label="Crop",
242
+ value=False,
243
+ )
244
+ margin = gr.Slider(
245
+ label="Margin",
246
+ minimum=0,
247
+ maximum=256,
248
+ step=16,
249
+ value=128,
250
+ )
251
+ with gr.Column():
252
+ max_dimension = gr.Slider(
253
+ label="Max Dimension",
254
+ minimum=256,
255
+ maximum=1024,
256
+ step=128,
257
+ value=512,
258
+ )
259
+
260
+ num_inference_steps = gr.Slider(
261
+ label="Number of inference steps",
262
+ minimum=4,
263
+ maximum=30,
264
+ step=2,
265
+ value=4,
266
+ )
267
+
268
+ # Change the number of inference steps based on the model type
269
+ model_type.change(
270
+ fn=lambda x: gr.update(value=4 if x == "schnell" else 28),
271
+ inputs=model_type,
272
+ outputs=num_inference_steps,
273
+ )
274
+
275
+ run_button.click(
276
+ fn=predict,
277
+ inputs=[
278
+ model_type,
279
+ image_and_mask,
280
+ condition_image,
281
+ prompt,
282
+ subfolder,
283
+ seed,
284
+ num_inference_steps,
285
+ max_dimension,
286
+ margin,
287
+ crop,
288
+ num_images_per_prompt,
289
+ ],
290
+ outputs=[results],
291
+ )
292
+
293
+
294
+ demo.launch()
pyproject.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "FurnitureBlendingDemoAPI"
3
+ version = "0.1.0"
4
+ description = "Furniture blending demo API"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "gradio",
9
+ "gradio-imageslider",
10
+ "pillow",
11
+ "requests",
12
+ "numpy",
13
+ ]
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ pillow
3
+ gradio_imageslider
4
+ requests
5
+ numpy