blanchon commited on
Commit
19f98dc
·
1 Parent(s): 4e8bfec
Files changed (6) hide show
  1. .python-version +1 -0
  2. README.md +25 -5
  3. app.py +181 -131
  4. pyproject.toml +20 -0
  5. requirements.txt +4 -1
  6. uv.lock +0 -0
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
README.md CHANGED
@@ -1,13 +1,33 @@
1
  ---
2
- title: FLUX.1 Fill Dev
3
- emoji: 🖌️
4
  colorFrom: green
5
- colorTo: purple
6
  sdk: gradio
 
7
  sdk_version: 5.6.0
 
8
  app_file: app.py
9
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
  ---
2
+ title: AnyFurnish
3
+ emoji: 🛋️
4
  colorFrom: green
5
+ colorTo: yellow
6
  sdk: gradio
7
+ python_version: 3.12
8
  sdk_version: 5.6.0
9
+ suggested_hardware: a100-large
10
  app_file: app.py
11
+ fullWidth: true
12
+ header: mini
13
+ # models: blanchon/anyfurnish
14
+ # datasets: blanchon/anyfurnish-dataset
15
+ tags:
16
+ - image-generation
17
+ - image-to-image
18
+ - furniture
19
+ - virtual-staging
20
+ - home-decor
21
+ - home-design
22
+ pinned: true
23
+ preload_from_hub:
24
+ - xiaozaa/flux1-fill-dev-diffusers
25
+ - blanchon/FluxFillFurniture
26
+ - black-forest-labs/FLUX.1-Fill-dev
27
  license: mit
28
  ---
29
 
30
+ # AnyFurnish
31
+
32
+ AnyFurnish is a tool that allows you to generate furniture images using Flux.1 Fill Dev.
33
+
app.py CHANGED
@@ -1,111 +1,164 @@
 
 
 
1
  import gradio as gr
2
  import numpy as np
3
-
4
  import spaces
5
  import torch
6
- import spaces
7
- import random
 
8
 
9
- from diffusers import FluxFillPipeline, FluxTransformer2DModel
10
- from PIL import Image
11
 
12
- device = "cuda"
13
  MAX_SEED = np.iinfo(np.int32).max
14
- MAX_IMAGE_SIZE = 2048
15
-
16
- # pipe = FluxFillPipeline.from_pretrained("black-forest-labs/FLUX.1-Fill-dev", torch_dtype=torch.bfloat16).to("cuda")
17
- transformer = FluxTransformer2DModel.from_pretrained(
18
- "xiaozaa/flux1-fill-dev-diffusers", ## The official Flux-Fill weights
19
- torch_dtype=torch.bfloat16
20
- )
21
- print("Start loading LoRA weights")
22
- state_dict, network_alphas = FluxFillPipeline.lora_state_dict(
23
- pretrained_model_name_or_path_or_dict="blanchon/FluxFillFurniture",
24
- weight_name="pytorch_lora_weights3.safetensors",
25
- return_alphas=True
26
- )
27
- is_correct_format = all("lora" in key or "dora_scale" in key for key in state_dict.keys())
28
- if not is_correct_format:
29
- raise ValueError("Invalid LoRA checkpoint.")
30
-
31
-
32
- pipe = FluxFillPipeline.from_pretrained(
33
- "black-forest-labs/FLUX.1-Fill-dev",
34
- torch_dtype=torch.bfloat16
35
- ).to(device)
36
- FluxFillPipeline.load_lora_into_transformer(
37
- state_dict=state_dict,
38
- network_alphas=network_alphas,
39
- transformer=pipe.transformer,
40
- )
41
-
42
- # pipe.load_lora_weights("blanchon/FluxFillFurniture", weight_name="lora_fill.safetensors")
43
- # pipe.fuse_lora(lora_scale=1.0)
44
- pipe.to("cuda")
45
-
46
- def calculate_optimal_dimensions(image: Image.Image):
47
- # Extract the original dimensions
48
- original_width, original_height = image.size
49
-
50
- # Set constants for enforcing a roughly 2:1 aspect ratio
51
- MIN_ASPECT_RATIO = 1.8
52
- MAX_ASPECT_RATIO = 2.2
53
- FIXED_DIMENSION = 1024
54
-
55
- # Calculate the aspect ratio of the original image
56
- original_aspect_ratio = original_width / original_height
57
-
58
- # Determine which dimension to fix
59
- if original_aspect_ratio > 1: # Wider than tall
60
- width = FIXED_DIMENSION
61
- height = round(FIXED_DIMENSION / original_aspect_ratio)
62
- else: # Taller than wide
63
- height = FIXED_DIMENSION
64
- width = round(FIXED_DIMENSION * original_aspect_ratio)
65
 
 
 
66
  # Ensure dimensions are multiples of 8
67
  width = (width // 8) * 8
68
  height = (height // 8) * 8
69
 
70
- # Enforce aspect ratio limits
71
- calculated_aspect_ratio = width / height
72
- if calculated_aspect_ratio > MAX_ASPECT_RATIO:
73
- width = (height * MAX_ASPECT_RATIO // 8) * 8
74
- elif calculated_aspect_ratio < MIN_ASPECT_RATIO:
75
- height = (width / MIN_ASPECT_RATIO // 8) * 8
76
 
77
- # Ensure width and height remain above the minimum dimensions
78
- width = max(width, 576) if width == FIXED_DIMENSION else width
79
- height = max(height, 576) if height == FIXED_DIMENSION else height
80
-
81
- return width, height
82
 
83
  @spaces.GPU
84
- def infer(edit_images, prompt, seed=42, randomize_seed=False, width=1024, height=1024, guidance_scale=3.5, num_inference_steps=28, progress=gr.Progress(track_tqdm=True)):
85
- image = edit_images["background"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  width, height = calculate_optimal_dimensions(image)
87
- mask = edit_images["layers"][0]
 
 
 
88
  if randomize_seed:
89
- seed = random.randint(0, MAX_SEED)
90
- image = pipe(
91
- prompt=prompt,
 
92
  image=image,
93
  mask_image=mask,
94
  height=height,
95
  width=width,
96
  guidance_scale=guidance_scale,
97
  num_inference_steps=num_inference_steps,
98
- generator=torch.Generator("cpu").manual_seed(seed)
99
- ).images[0]
100
- return image, seed
101
-
102
- examples = [
103
- "a tiny astronaut hatching from an egg on the moon",
104
- "a cat holding a sign that says hello world",
105
- "an anime illustration of a wiener schnitzel",
106
- ]
107
-
108
- css="""
 
 
 
 
 
 
 
 
109
  #col-container {
110
  margin: 0 auto;
111
  max-width: 1000px;
@@ -113,23 +166,27 @@ css="""
113
  """
114
 
115
  with gr.Blocks(css=css) as demo:
116
-
117
  with gr.Column(elem_id="col-container"):
118
- gr.Markdown(f"""# FLUX.1 Fill [dev]
119
- 12B param rectified flow transformer structural conditioning tuned, guidance-distilled from [FLUX.1 [pro]](https://blackforestlabs.ai/)
120
- [[non-commercial license](https://huggingface.co/black-forest-labs/FLUX.1-dev/blob/main/LICENSE.md)] [[blog](https://blackforestlabs.ai/announcing-black-forest-labs/)] [[model](https://huggingface.co/black-forest-labs/FLUX.1-dev)]
121
- """)
122
  with gr.Row():
123
  with gr.Column():
124
- edit_image = gr.ImageEditor(
125
- label='Upload and draw mask for inpainting',
126
- type='pil',
127
- sources=["upload", "webcam"],
128
- image_mode='RGB',
129
- layers=False,
130
- brush=gr.Brush(colors=["#FFFFFF"], color_mode="fixed"),
131
- height=600
132
- )
 
 
 
 
 
 
 
 
133
  prompt = gr.Text(
134
  label="Prompt",
135
  show_label=False,
@@ -138,11 +195,17 @@ with gr.Blocks(css=css) as demo:
138
  container=False,
139
  )
140
  run_button = gr.Button("Run")
141
-
142
- result = gr.Image(label="Result", show_label=False)
143
-
 
 
 
 
 
 
 
144
  with gr.Accordion("Advanced Settings", open=False):
145
-
146
  seed = gr.Slider(
147
  label="Seed",
148
  minimum=0,
@@ -150,31 +213,10 @@ with gr.Blocks(css=css) as demo:
150
  step=1,
151
  value=0,
152
  )
153
-
154
  randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
155
-
156
- with gr.Row():
157
-
158
- width = gr.Slider(
159
- label="Width",
160
- minimum=256,
161
- maximum=MAX_IMAGE_SIZE,
162
- step=32,
163
- value=1024,
164
- visible=False
165
- )
166
-
167
- height = gr.Slider(
168
- label="Height",
169
- minimum=256,
170
- maximum=MAX_IMAGE_SIZE,
171
- step=32,
172
- value=1024,
173
- visible=False
174
- )
175
-
176
- with gr.Row():
177
 
 
178
  guidance_scale = gr.Slider(
179
  label="Guidance Scale",
180
  minimum=1,
@@ -182,7 +224,7 @@ with gr.Blocks(css=css) as demo:
182
  step=0.5,
183
  value=50,
184
  )
185
-
186
  num_inference_steps = gr.Slider(
187
  label="Number of inference steps",
188
  minimum=1,
@@ -193,9 +235,17 @@ with gr.Blocks(css=css) as demo:
193
 
194
  gr.on(
195
  triggers=[run_button.click, prompt.submit],
196
- fn = infer,
197
- inputs = [edit_image, prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps],
198
- outputs = [result, seed]
 
 
 
 
 
 
 
 
199
  )
200
 
201
- demo.launch()
 
1
+ import secrets
2
+ from typing import cast
3
+
4
  import gradio as gr
5
  import numpy as np
 
6
  import spaces
7
  import torch
8
+ from diffusers import FluxFillPipeline
9
+ from gradio.components.image_editor import EditorValue
10
+ from PIL import Image, ImageOps
11
 
12
+ DEVICE = "cuda"
 
13
 
 
14
  MAX_SEED = np.iinfo(np.int32).max
15
+ FIXED_DIMENSION = 900
16
+
17
+ SYSTEM_PROMPT = r"""This two-panel split-frame image showcases a furniture in as a product shot versus styled in a room.
18
+ [LEFT] standalone product shot image the furniture on a white background.
19
+ [RIGHT] integrated example within a room scene."""
20
+
21
+ if not torch.cuda.is_available():
22
+
23
+ def _dummy_pipe(image: Image.Image, *args, **kwargs): # noqa: ARG001
24
+ return {"images": [image]}
25
+
26
+ pipe = _dummy_pipe
27
+ else:
28
+ state_dict, network_alphas = FluxFillPipeline.lora_state_dict(
29
+ pretrained_model_name_or_path_or_dict="blanchon/FluxFillFurniture",
30
+ weight_name="pytorch_lora_weights3.safetensors",
31
+ return_alphas=True,
32
+ )
33
+
34
+ if not all(("lora" in key or "dora_scale" in key) for key in state_dict):
35
+ msg = "Invalid LoRA checkpoint."
36
+ raise ValueError(msg)
37
+
38
+ pipe = FluxFillPipeline.from_pretrained(
39
+ "black-forest-labs/FLUX.1-Fill-dev", torch_dtype=torch.bfloat16
40
+ ).to(DEVICE)
41
+ FluxFillPipeline.load_lora_into_transformer(
42
+ state_dict=state_dict,
43
+ network_alphas=network_alphas,
44
+ transformer=pipe.transformer,
45
+ )
46
+ pipe.to(DEVICE)
47
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ def calculate_optimal_dimensions(image: Image.Image) -> tuple[int, int]:
50
+ width, height = image.size
51
  # Ensure dimensions are multiples of 8
52
  width = (width // 8) * 8
53
  height = (height // 8) * 8
54
 
55
+ return int(width), int(height)
 
 
 
 
 
56
 
 
 
 
 
 
57
 
58
  @spaces.GPU
59
+ def infer(
60
+ furniture_image: Image.Image,
61
+ room_image: EditorValue,
62
+ prompt,
63
+ seed=42,
64
+ randomize_seed=False,
65
+ guidance_scale=3.5,
66
+ num_inference_steps=28,
67
+ progress=gr.Progress(track_tqdm=True), # noqa: ARG001, B008
68
+ ):
69
+ _room_image = room_image["background"]
70
+ if _room_image is None:
71
+ msg = "Room image is required"
72
+ raise ValueError(msg)
73
+ _room_image = cast(Image.Image, _room_image)
74
+ _room_image = ImageOps.fit(
75
+ _room_image,
76
+ (FIXED_DIMENSION, FIXED_DIMENSION),
77
+ method=Image.Resampling.LANCZOS,
78
+ centering=(0.5, 0.5),
79
+ )
80
+
81
+ _room_mask = room_image["layers"][0]
82
+ if _room_mask is None:
83
+ msg = "Room mask is required"
84
+ raise ValueError(msg)
85
+ _room_mask = cast(Image.Image, _room_mask)
86
+ _room_mask = ImageOps.fit(
87
+ _room_mask,
88
+ (FIXED_DIMENSION, FIXED_DIMENSION),
89
+ method=Image.Resampling.LANCZOS,
90
+ centering=(0.5, 0.5),
91
+ )
92
+
93
+ furniture_image = ImageOps.fit(
94
+ furniture_image,
95
+ (FIXED_DIMENSION, FIXED_DIMENSION),
96
+ method=Image.Resampling.LANCZOS,
97
+ centering=(0.5, 0.5),
98
+ )
99
+ _furniture_image = Image.new(
100
+ "RGB",
101
+ (FIXED_DIMENSION, FIXED_DIMENSION),
102
+ (255, 255, 255),
103
+ )
104
+ _furniture_image.paste(furniture_image, (0, 0))
105
+
106
+ _furniture_mask = Image.new(
107
+ "RGB", (FIXED_DIMENSION, FIXED_DIMENSION), (255, 255, 255)
108
+ )
109
+
110
+ image = Image.new(
111
+ "RGB",
112
+ (FIXED_DIMENSION * 2, FIXED_DIMENSION),
113
+ (255, 255, 255),
114
+ )
115
+ # Paste on the center of the image
116
+ image.paste(_furniture_image, (0, 0))
117
+ image.paste(_room_image, (FIXED_DIMENSION, 0))
118
+
119
+ mask = Image.new(
120
+ "RGB",
121
+ (FIXED_DIMENSION * 2, FIXED_DIMENSION),
122
+ (255, 255, 255),
123
+ )
124
+ mask.paste(_furniture_mask, (0, 0))
125
+ mask.paste(_room_mask, (FIXED_DIMENSION, 0))
126
+
127
  width, height = calculate_optimal_dimensions(image)
128
+ # Resize the image and mask to the optimal dimensions for the VAe
129
+ image = image.resize((width, height))
130
+ mask = mask.resize((width, height))
131
+
132
  if randomize_seed:
133
+ seed = secrets.randbelow(MAX_SEED)
134
+
135
+ results_images = pipe(
136
+ prompt=prompt + ".\n" + SYSTEM_PROMPT,
137
  image=image,
138
  mask_image=mask,
139
  height=height,
140
  width=width,
141
  guidance_scale=guidance_scale,
142
  num_inference_steps=num_inference_steps,
143
+ batch_size=4,
144
+ generator=torch.Generator("cpu").manual_seed(seed),
145
+ )["images"]
146
+
147
+ cropped_images = [
148
+ image.crop((FIXED_DIMENSION, 0, FIXED_DIMENSION * 2, FIXED_DIMENSION))
149
+ for image in results_images
150
+ ]
151
+
152
+ return cropped_images, seed
153
+
154
+
155
+ intro_markdown = """
156
+ # AnyFurnish
157
+
158
+ AnyFurnish is a tool that allows you to generate furniture images using Flux.1 Fill Dev.
159
+ """
160
+
161
+ css = """
162
  #col-container {
163
  margin: 0 auto;
164
  max-width: 1000px;
 
166
  """
167
 
168
  with gr.Blocks(css=css) as demo:
 
169
  with gr.Column(elem_id="col-container"):
170
+ gr.Markdown(intro_markdown)
 
 
 
171
  with gr.Row():
172
  with gr.Column():
173
+ with gr.Column():
174
+ furniture_image = gr.Image(
175
+ label="Furniture Image",
176
+ type="pil",
177
+ sources=["upload"],
178
+ image_mode="RGB",
179
+ height=300,
180
+ )
181
+ room_image = gr.ImageEditor(
182
+ label="Room Image - Draw mask for inpainting",
183
+ type="pil",
184
+ sources=["upload"],
185
+ image_mode="RGB",
186
+ layers=False,
187
+ brush=gr.Brush(colors=["#FFFFFF"], color_mode="fixed"),
188
+ height=300,
189
+ )
190
  prompt = gr.Text(
191
  label="Prompt",
192
  show_label=False,
 
195
  container=False,
196
  )
197
  run_button = gr.Button("Run")
198
+
199
+ results = gr.Gallery(
200
+ label="Results",
201
+ format="png",
202
+ show_label=False,
203
+ columns=2,
204
+ height=600,
205
+ preview=True,
206
+ )
207
+
208
  with gr.Accordion("Advanced Settings", open=False):
 
209
  seed = gr.Slider(
210
  label="Seed",
211
  minimum=0,
 
213
  step=1,
214
  value=0,
215
  )
216
+
217
  randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
+ with gr.Row():
220
  guidance_scale = gr.Slider(
221
  label="Guidance Scale",
222
  minimum=1,
 
224
  step=0.5,
225
  value=50,
226
  )
227
+
228
  num_inference_steps = gr.Slider(
229
  label="Number of inference steps",
230
  minimum=1,
 
235
 
236
  gr.on(
237
  triggers=[run_button.click, prompt.submit],
238
+ fn=infer,
239
+ inputs=[
240
+ furniture_image,
241
+ room_image,
242
+ prompt,
243
+ seed,
244
+ randomize_seed,
245
+ guidance_scale,
246
+ num_inference_steps,
247
+ ],
248
+ outputs=[results, seed],
249
  )
250
 
251
+ demo.launch()
pyproject.toml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "flux-1-fill-dev"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "accelerate>=1.2.1",
9
+ "diffusers",
10
+ "gradio>=5.12.0",
11
+ "peft>=0.14.0",
12
+ "pillow>=11.1.0",
13
+ "safetensors>=0.5.2",
14
+ "sentencepiece>=0.2.0",
15
+ "spaces>=0.32.0",
16
+ "transformers>=4.48.0",
17
+ ]
18
+
19
+ [tool.uv.sources]
20
+ diffusers = { git = "https://github.com/huggingface/diffusers.git" }
requirements.txt CHANGED
@@ -3,4 +3,7 @@ transformers
3
  accelerate
4
  safetensors
5
  sentencepiece
6
- peft
 
 
 
 
3
  accelerate
4
  safetensors
5
  sentencepiece
6
+ peft
7
+ gradio
8
+ spaces
9
+ pillow
uv.lock ADDED
The diff for this file is too large to render. See raw diff