Spaces:
Configuration error
Configuration error
update gradio demo
Browse files- README.md +9 -0
- app.py +0 -6
- backend.py +20 -28
- presets.py +1 -0
README.md
CHANGED
@@ -9,4 +9,13 @@ app_file: app.py
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
+
# Face Editor
|
13 |
+
This face editor uses a CelebA pretrained VQGAN with CLIP to allow prompt-based image manipulation, as well as slider based manipulation using extracted latent vectors.
|
14 |
+
|
15 |
+
I've written a series of Medium articles which provide a detailed and beginner-friendly explanation of how this was built.
|
16 |
+
|
17 |
+
## Features:
|
18 |
+
Edit masking using custom backpropagation hook
|
19 |
+
|
20 |
+
|
21 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
@@ -183,17 +183,11 @@ with gr.Blocks(css="styles.css") as demo:
|
|
183 |
value=3,
|
184 |
step=1,
|
185 |
label="Steps to run at the end of the optimization, optimizing only the masked perceptual loss. If the edit is changing the identity too much, this setting will run steps at the end that 'pull' the image back towards the original identity")
|
186 |
-
# discriminator_steps = gr.Slider(minimum=0,
|
187 |
-
# maximum=50,
|
188 |
-
# step=1,
|
189 |
-
# value=0,
|
190 |
-
# label="Steps to run at the end, optimizing only the discriminator loss. This helps to reduce artefacts, but because the model is trained on CelebA, this will make your generations look more like generic white celebrities")
|
191 |
clear.click(StateWrapper.clear_transforms, inputs=[state], outputs=[state, out, mask])
|
192 |
asian_weight.change(StateWrapper.apply_asian_vector, inputs=[state, asian_weight], outputs=[state, out, mask])
|
193 |
lip_size.change(StateWrapper.apply_lip_vector, inputs=[state, lip_size], outputs=[state, out, mask])
|
194 |
blue_eyes.change(StateWrapper.apply_rb_vector, inputs=[state, blue_eyes], outputs=[state, out, mask])
|
195 |
blend_weight.change(StateWrapper.blend, inputs=[state, blend_weight], outputs=[state, out, mask])
|
196 |
-
# requantize.change(StateWrapper.update_requant, inputs=[state, requantize], outputs=[state, out, mask])
|
197 |
base_img.change(StateWrapper.update_images, inputs=[state, base_img, blend_img, blend_weight], outputs=[state, out, mask])
|
198 |
blend_img.change(StateWrapper.update_images, inputs=[state, base_img, blend_img, blend_weight], outputs=[state, out, mask])
|
199 |
apply_prompts.click(StateWrapper.apply_prompts, inputs=[state, positive_prompts, negative_prompts, learning_rate, iterations, lpips_weight, reconstruction_steps], outputs=[state, out, mask])
|
|
|
183 |
value=3,
|
184 |
step=1,
|
185 |
label="Steps to run at the end of the optimization, optimizing only the masked perceptual loss. If the edit is changing the identity too much, this setting will run steps at the end that 'pull' the image back towards the original identity")
|
|
|
|
|
|
|
|
|
|
|
186 |
clear.click(StateWrapper.clear_transforms, inputs=[state], outputs=[state, out, mask])
|
187 |
asian_weight.change(StateWrapper.apply_asian_vector, inputs=[state, asian_weight], outputs=[state, out, mask])
|
188 |
lip_size.change(StateWrapper.apply_lip_vector, inputs=[state, lip_size], outputs=[state, out, mask])
|
189 |
blue_eyes.change(StateWrapper.apply_rb_vector, inputs=[state, blue_eyes], outputs=[state, out, mask])
|
190 |
blend_weight.change(StateWrapper.blend, inputs=[state, blend_weight], outputs=[state, out, mask])
|
|
|
191 |
base_img.change(StateWrapper.update_images, inputs=[state, base_img, blend_img, blend_weight], outputs=[state, out, mask])
|
192 |
blend_img.change(StateWrapper.update_images, inputs=[state, base_img, blend_img, blend_weight], outputs=[state, out, mask])
|
193 |
apply_prompts.click(StateWrapper.apply_prompts, inputs=[state, positive_prompts, negative_prompts, learning_rate, iterations, lpips_weight, reconstruction_steps], outputs=[state, out, mask])
|
backend.py
CHANGED
@@ -79,7 +79,7 @@ class ImagePromptEditor(nn.Module):
|
|
79 |
self.latent = latent.detach().to(self.device)
|
80 |
|
81 |
def set_params(self, lr, iterations, lpips_weight, reconstruction_steps, attn_mask):
|
82 |
-
self.
|
83 |
self.iterations = iterations
|
84 |
self.lr = lr
|
85 |
self.lpips_weight = lpips_weight
|
@@ -118,25 +118,16 @@ class ImagePromptEditor(nn.Module):
|
|
118 |
loss = -torch.log(pos_logits) + torch.log(neg_logits)
|
119 |
return loss
|
120 |
|
121 |
-
def
|
122 |
-
if self.make_grid:
|
123 |
-
self.index += 1
|
124 |
-
plt.subplot(1, 13, self.index)
|
125 |
-
plt.imshow(get_pil(processed_img[0]).detach().cpu())
|
126 |
-
else:
|
127 |
-
plt.imshow(get_pil(processed_img[0]).detach().cpu())
|
128 |
-
plt.show()
|
129 |
-
|
130 |
-
def _attn_mask(self, grad):
|
131 |
newgrad = grad
|
132 |
-
if self.
|
133 |
-
newgrad = grad * (self.
|
134 |
return newgrad
|
135 |
|
136 |
-
def
|
137 |
newgrad = grad
|
138 |
-
if self.
|
139 |
-
newgrad = grad * ((self.
|
140 |
return newgrad
|
141 |
|
142 |
def _get_next_inputs(self, transformed_img):
|
@@ -144,11 +135,11 @@ class ImagePromptEditor(nn.Module):
|
|
144 |
processed_img.retain_grad()
|
145 |
|
146 |
lpips_input = processed_img.clone()
|
147 |
-
lpips_input.register_hook(self.
|
148 |
lpips_input.retain_grad()
|
149 |
|
150 |
clip_input = processed_img.clone()
|
151 |
-
clip_input.register_hook(self.
|
152 |
clip_input.retain_grad()
|
153 |
|
154 |
return (processed_img, lpips_input, clip_input)
|
@@ -160,15 +151,15 @@ class ImagePromptEditor(nn.Module):
|
|
160 |
processed_img, lpips_input, clip_input = self._get_next_inputs(
|
161 |
transformed_img
|
162 |
)
|
163 |
-
with torch.autocast("cuda"):
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
if log:
|
173 |
wandb.log({"Perceptual Loss": perceptual_loss})
|
174 |
wandb.log({"CLIP Loss": clip_loss})
|
@@ -188,7 +179,7 @@ class ImagePromptEditor(nn.Module):
|
|
188 |
processed_img.retain_grad()
|
189 |
|
190 |
lpips_input = processed_img.clone()
|
191 |
-
lpips_input.register_hook(self.
|
192 |
lpips_input.retain_grad()
|
193 |
with torch.autocast("cuda"):
|
194 |
perceptual_loss = (
|
@@ -217,4 +208,5 @@ class ImagePromptEditor(nn.Module):
|
|
217 |
print("Running LPIPS optim only")
|
218 |
for transform in self._optimize_LPIPS(vector, original_img, optim):
|
219 |
yield transform
|
|
|
220 |
yield vector if self.return_val == "vector" else self.latent + vector
|
|
|
79 |
self.latent = latent.detach().to(self.device)
|
80 |
|
81 |
def set_params(self, lr, iterations, lpips_weight, reconstruction_steps, attn_mask):
|
82 |
+
self.attn_mask = attn_mask
|
83 |
self.iterations = iterations
|
84 |
self.lr = lr
|
85 |
self.lpips_weight = lpips_weight
|
|
|
118 |
loss = -torch.log(pos_logits) + torch.log(neg_logits)
|
119 |
return loss
|
120 |
|
121 |
+
def _apply_mask(self, grad):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
newgrad = grad
|
123 |
+
if self.attn_mask is not None:
|
124 |
+
newgrad = grad * (self.attn_mask)
|
125 |
return newgrad
|
126 |
|
127 |
+
def _apply_inverse_mask(self, grad):
|
128 |
newgrad = grad
|
129 |
+
if self.attn_mask is not None:
|
130 |
+
newgrad = grad * ((self.attn_mask - 1) * -1)
|
131 |
return newgrad
|
132 |
|
133 |
def _get_next_inputs(self, transformed_img):
|
|
|
135 |
processed_img.retain_grad()
|
136 |
|
137 |
lpips_input = processed_img.clone()
|
138 |
+
lpips_input.register_hook(self._apply_inverse_mask)
|
139 |
lpips_input.retain_grad()
|
140 |
|
141 |
clip_input = processed_img.clone()
|
142 |
+
clip_input.register_hook(self._apply_mask)
|
143 |
clip_input.retain_grad()
|
144 |
|
145 |
return (processed_img, lpips_input, clip_input)
|
|
|
151 |
processed_img, lpips_input, clip_input = self._get_next_inputs(
|
152 |
transformed_img
|
153 |
)
|
154 |
+
# with torch.autocast("cuda"):
|
155 |
+
clip_loss = self._get_CLIP_loss(pos_prompts, neg_prompts, clip_input)
|
156 |
+
print("CLIP loss", clip_loss)
|
157 |
+
perceptual_loss = (
|
158 |
+
self.perceptual_loss(lpips_input, original_img.clone())
|
159 |
+
* self.lpips_weight
|
160 |
+
)
|
161 |
+
print("LPIPS loss: ", perceptual_loss)
|
162 |
+
print("Sum Loss", perceptual_loss + clip_loss)
|
163 |
if log:
|
164 |
wandb.log({"Perceptual Loss": perceptual_loss})
|
165 |
wandb.log({"CLIP Loss": clip_loss})
|
|
|
179 |
processed_img.retain_grad()
|
180 |
|
181 |
lpips_input = processed_img.clone()
|
182 |
+
lpips_input.register_hook(self._apply_inverse_mask)
|
183 |
lpips_input.retain_grad()
|
184 |
with torch.autocast("cuda"):
|
185 |
perceptual_loss = (
|
|
|
208 |
print("Running LPIPS optim only")
|
209 |
for transform in self._optimize_LPIPS(vector, original_img, optim):
|
210 |
yield transform
|
211 |
+
|
212 |
yield vector if self.return_val == "vector" else self.latent + vector
|
presets.py
CHANGED
@@ -2,6 +2,7 @@ import gradio as gr
|
|
2 |
|
3 |
|
4 |
def set_preset(config_str):
|
|
|
5 |
choices = [
|
6 |
"Small Masked Changes (e.g. add lipstick)",
|
7 |
"Major Masked Changes (e.g. change hair color or nose size)",
|
|
|
2 |
|
3 |
|
4 |
def set_preset(config_str):
|
5 |
+
print(config_str)
|
6 |
choices = [
|
7 |
"Small Masked Changes (e.g. add lipstick)",
|
8 |
"Major Masked Changes (e.g. change hair color or nose size)",
|