Spaces:
Sleeping
Sleeping
new-cnn (#1)
Browse files- trying new cnn (3601849835ba31be17f768a180841e562399333c)
- app.py +39 -28
- clip_styler.py +212 -0
- cnn1.py +251 -0
- requirements.txt +2 -1
- style_net.py +62 -62
- util.py +168 -0
app.py
CHANGED
|
@@ -4,21 +4,39 @@ import clip
|
|
| 4 |
from torchvision import transforms
|
| 5 |
from style_net import StyleNet
|
| 6 |
from dataset import FlickrStreamer
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# --- CONFIGURATION ---
|
| 9 |
-
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 10 |
INDEX_PATH = "flickr_embeddings.pt"
|
| 11 |
SEARCH_LIMIT = 5000 # Must match the indexer limit!
|
| 12 |
# ---------------------
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
print(f"System starting on {DEVICE}...")
|
| 15 |
|
| 16 |
# 1. LOAD MODELS
|
| 17 |
-
# A.
|
| 18 |
-
|
| 19 |
|
| 20 |
-
# B.
|
| 21 |
-
|
| 22 |
|
| 23 |
# 2. LOAD SEARCH INDEX
|
| 24 |
print("Loading Search Index...")
|
|
@@ -60,32 +78,25 @@ def search_flickr(text_query):
|
|
| 60 |
|
| 61 |
return results
|
| 62 |
|
| 63 |
-
# --- FUNCTION: EDIT ---
|
| 64 |
-
tf_edit = transforms.Compose([
|
| 65 |
-
transforms.Resize((256, 256)),
|
| 66 |
-
transforms.ToTensor()
|
| 67 |
-
])
|
| 68 |
-
|
| 69 |
def edit_image(image, style_choice):
|
| 70 |
-
if image is None
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
try:
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
return image
|
| 82 |
-
|
| 83 |
-
input_tensor = tf_edit(image).unsqueeze(0).to(DEVICE)
|
| 84 |
-
with torch.no_grad():
|
| 85 |
-
output_tensor = style_net(input_tensor)
|
| 86 |
-
|
| 87 |
-
output_img = transforms.ToPILImage()(output_tensor.squeeze().cpu())
|
| 88 |
-
return output_img
|
| 89 |
|
| 90 |
# --- THE UI ---
|
| 91 |
with gr.Blocks() as demo:
|
|
|
|
| 4 |
from torchvision import transforms
|
| 5 |
from style_net import StyleNet
|
| 6 |
from dataset import FlickrStreamer
|
| 7 |
+
from util import DEVICE
|
| 8 |
+
from clip_styler import ClipStyler
|
| 9 |
|
| 10 |
# --- CONFIGURATION ---
|
| 11 |
+
#DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 12 |
INDEX_PATH = "flickr_embeddings.pt"
|
| 13 |
SEARCH_LIMIT = 5000 # Must match the indexer limit!
|
| 14 |
# ---------------------
|
| 15 |
|
| 16 |
+
UI_EPOCHS = 100
|
| 17 |
+
STYLE_PRESETS = {
|
| 18 |
+
"Sketch": {
|
| 19 |
+
"prompt": "charcoal sketch style",
|
| 20 |
+
"source": "a Photo",
|
| 21 |
+
},
|
| 22 |
+
"Van Gogh": {
|
| 23 |
+
"prompt": "Van Gogh painting style",
|
| 24 |
+
"source": "a Photo",
|
| 25 |
+
},
|
| 26 |
+
"Cyberpunk": {
|
| 27 |
+
"prompt": "neon cyberpunk style",
|
| 28 |
+
"source": "a Photo",
|
| 29 |
+
},
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
print(f"System starting on {DEVICE}...")
|
| 33 |
|
| 34 |
# 1. LOAD MODELS
|
| 35 |
+
# A. Retrieval Model (CLIP)
|
| 36 |
+
clip_model, _ = clip.load("ViT-B/32", device=DEVICE)
|
| 37 |
|
| 38 |
+
# B. Editing Model (CLIP-guided optimizer)
|
| 39 |
+
clip_styler = ClipStyler(device=DEVICE, num_epochs=UI_EPOCHS)
|
| 40 |
|
| 41 |
# 2. LOAD SEARCH INDEX
|
| 42 |
print("Loading Search Index...")
|
|
|
|
| 78 |
|
| 79 |
return results
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
def edit_image(image, style_choice):
|
| 82 |
+
if image is None or style_choice is None:
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
preset = STYLE_PRESETS.get(style_choice)
|
| 86 |
+
if preset is None:
|
| 87 |
+
return image
|
| 88 |
+
|
|
|
|
| 89 |
try:
|
| 90 |
+
return clip_styler.stylize(
|
| 91 |
+
image=image,
|
| 92 |
+
prompt=preset["prompt"],
|
| 93 |
+
source=preset.get("source", "a Photo"),
|
| 94 |
+
num_epochs=UI_EPOCHS,
|
| 95 |
+
log_interval=0,
|
| 96 |
+
)
|
| 97 |
+
except Exception as exc:
|
| 98 |
+
print(f"[edit_image] Failed to stylize image: {exc}")
|
| 99 |
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
# --- THE UI ---
|
| 102 |
with gr.Blocks() as demo:
|
clip_styler.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import torch
|
| 7 |
+
import torch.optim as optim
|
| 8 |
+
from torchvision import transforms, models
|
| 9 |
+
from torchvision.transforms.functional import adjust_contrast
|
| 10 |
+
from transformers import CLIPModel, AutoTokenizer
|
| 11 |
+
|
| 12 |
+
from cnn1 import UNet
|
| 13 |
+
from util import (
|
| 14 |
+
DEVICE,
|
| 15 |
+
IMG_SIZE,
|
| 16 |
+
LR,
|
| 17 |
+
CROP_SIZE,
|
| 18 |
+
RESIZE,
|
| 19 |
+
NUM_EPOCHS,
|
| 20 |
+
NUM_CROPS,
|
| 21 |
+
PATCH_THRESHOLD,
|
| 22 |
+
L_TV,
|
| 23 |
+
L_PATCH,
|
| 24 |
+
L_DIR,
|
| 25 |
+
L_CONTENT,
|
| 26 |
+
normalize,
|
| 27 |
+
clip_normalize,
|
| 28 |
+
get_features,
|
| 29 |
+
prompt_ensemble,
|
| 30 |
+
get_image_prior_losses,
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class ClipStyler:
|
| 35 |
+
"""
|
| 36 |
+
Wraps the CLIP-guided optimization loop from main.py so it can be reused
|
| 37 |
+
by both the CLI entrypoint and the Gradio application.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
device: Optional[torch.device] = None,
|
| 43 |
+
num_epochs: int = NUM_EPOCHS,
|
| 44 |
+
) -> None:
|
| 45 |
+
self.device = device or DEVICE
|
| 46 |
+
self.num_epochs = num_epochs
|
| 47 |
+
|
| 48 |
+
# Text/image encoders
|
| 49 |
+
self.clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(
|
| 50 |
+
self.device
|
| 51 |
+
)
|
| 52 |
+
self.clip_model.eval()
|
| 53 |
+
for param in self.clip_model.parameters():
|
| 54 |
+
param.requires_grad_(False)
|
| 55 |
+
|
| 56 |
+
self.tokenizer = AutoTokenizer.from_pretrained("openai/clip-vit-base-patch32")
|
| 57 |
+
|
| 58 |
+
# Perceptual backbone
|
| 59 |
+
self.vgg19 = models.vgg19(pretrained=True).features.to(self.device).eval()
|
| 60 |
+
for param in self.vgg19.parameters():
|
| 61 |
+
param.requires_grad_(False)
|
| 62 |
+
|
| 63 |
+
# Image helpers
|
| 64 |
+
self.image_transform = transforms.Compose(
|
| 65 |
+
[transforms.Resize((IMG_SIZE, IMG_SIZE)), transforms.ToTensor()]
|
| 66 |
+
)
|
| 67 |
+
self.to_pil = transforms.ToPILImage()
|
| 68 |
+
self.crop_transform = transforms.RandomCrop(CROP_SIZE)
|
| 69 |
+
self.augment_transform = transforms.Compose(
|
| 70 |
+
[
|
| 71 |
+
transforms.RandomPerspective(fill=0, p=1, distortion_scale=0.5),
|
| 72 |
+
transforms.Resize(RESIZE),
|
| 73 |
+
]
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
def stylize(
|
| 77 |
+
self,
|
| 78 |
+
image: Image.Image,
|
| 79 |
+
prompt: str,
|
| 80 |
+
source: str = "a Photo",
|
| 81 |
+
num_epochs: Optional[int] = None,
|
| 82 |
+
log_interval: int = 20,
|
| 83 |
+
) -> Image.Image:
|
| 84 |
+
"""
|
| 85 |
+
Run the CLIP-guided optimization loop and return the stylized PIL image.
|
| 86 |
+
"""
|
| 87 |
+
if image is None:
|
| 88 |
+
raise ValueError("Image must be provided for stylization.")
|
| 89 |
+
|
| 90 |
+
epochs = num_epochs if num_epochs is not None else self.num_epochs
|
| 91 |
+
base_img = self._pil_to_tensor(image)
|
| 92 |
+
content_features = get_features(normalize(base_img), self.vgg19)
|
| 93 |
+
|
| 94 |
+
cnn_model = UNet(text_dim=512).to(self.device)
|
| 95 |
+
cnn_model.train()
|
| 96 |
+
|
| 97 |
+
optimizer = optim.Adam(cnn_model.parameters(), lr=LR)
|
| 98 |
+
scheduler = torch.optim.lr_scheduler.StepLR(
|
| 99 |
+
optimizer, step_size=100, gamma=0.5
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
with torch.no_grad():
|
| 103 |
+
text_features = self._encode_text(prompt)
|
| 104 |
+
source_features = self._encode_text(source)
|
| 105 |
+
base_img_features = self._image_features(base_img)
|
| 106 |
+
|
| 107 |
+
style_direction = text_features - source_features
|
| 108 |
+
style_direction /= style_direction.norm(dim=-1, keepdim=True)
|
| 109 |
+
|
| 110 |
+
final_target = base_img
|
| 111 |
+
for epoch in range(epochs + 1):
|
| 112 |
+
scheduler.step()
|
| 113 |
+
target = cnn_model(base_img, text_embedding=text_features)
|
| 114 |
+
final_target = target
|
| 115 |
+
|
| 116 |
+
target_features = get_features(normalize(target), self.vgg19)
|
| 117 |
+
content_loss = torch.mean(
|
| 118 |
+
(target_features["conv4_2"] - content_features["conv4_2"]) ** 2
|
| 119 |
+
)
|
| 120 |
+
content_loss += torch.mean(
|
| 121 |
+
(target_features["conv5_2"] - content_features["conv5_2"]) ** 2
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
img_aug = self._augment_crops(target)
|
| 125 |
+
img_aug_features = self.clip_model.get_image_features(
|
| 126 |
+
pixel_values=clip_normalize(img_aug)
|
| 127 |
+
)
|
| 128 |
+
img_aug_features /= img_aug_features.norm(dim=-1, keepdim=True)
|
| 129 |
+
|
| 130 |
+
img_direction = img_aug_features - base_img_features
|
| 131 |
+
img_direction /= img_direction.norm(dim=-1, keepdim=True)
|
| 132 |
+
|
| 133 |
+
patch_text_direction = style_direction.repeat(img_direction.size(0), 1)
|
| 134 |
+
patch_text_direction /= patch_text_direction.norm(dim=-1, keepdim=True)
|
| 135 |
+
|
| 136 |
+
loss_patch = 1 - torch.cosine_similarity(
|
| 137 |
+
img_direction, patch_text_direction, dim=1
|
| 138 |
+
)
|
| 139 |
+
loss_patch = torch.where(
|
| 140 |
+
loss_patch < PATCH_THRESHOLD, torch.zeros_like(loss_patch), loss_patch
|
| 141 |
+
).mean()
|
| 142 |
+
|
| 143 |
+
glob_features = self.clip_model.get_image_features(
|
| 144 |
+
pixel_values=clip_normalize(target)
|
| 145 |
+
)
|
| 146 |
+
glob_features /= glob_features.norm(dim=-1, keepdim=True)
|
| 147 |
+
|
| 148 |
+
glob_direction = glob_features - base_img_features
|
| 149 |
+
glob_direction /= glob_direction.norm(dim=-1, keepdim=True)
|
| 150 |
+
|
| 151 |
+
loss_glob = (1 - torch.cosine_similarity(glob_direction, style_direction, dim=1)).mean()
|
| 152 |
+
|
| 153 |
+
loss_tv = L_TV * get_image_prior_losses(target)
|
| 154 |
+
|
| 155 |
+
total_loss = (
|
| 156 |
+
(L_PATCH * loss_patch)
|
| 157 |
+
+ (L_DIR * loss_glob)
|
| 158 |
+
+ (L_CONTENT * content_loss)
|
| 159 |
+
+ loss_tv
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
optimizer.zero_grad()
|
| 163 |
+
total_loss.backward()
|
| 164 |
+
optimizer.step()
|
| 165 |
+
|
| 166 |
+
'''
|
| 167 |
+
if log_interval and epoch % log_interval == 0:
|
| 168 |
+
print(
|
| 169 |
+
f"[ClipStyler] Epoch {epoch}: total={total_loss.item():.4f}, "
|
| 170 |
+
f"content={content_loss.item():.4f}, patch={loss_patch.item():.4f}, "
|
| 171 |
+
f"dir={loss_glob.item():.4f}, tv={loss_tv.item():.4f}"
|
| 172 |
+
)
|
| 173 |
+
'''
|
| 174 |
+
|
| 175 |
+
return self._tensor_to_pil(final_target)
|
| 176 |
+
|
| 177 |
+
def _pil_to_tensor(self, image: Image.Image) -> torch.Tensor:
|
| 178 |
+
if image.mode != "RGB":
|
| 179 |
+
image = image.convert("RGB")
|
| 180 |
+
tensor = self.image_transform(image).unsqueeze(0)
|
| 181 |
+
return tensor.to(self.device)
|
| 182 |
+
|
| 183 |
+
def _tensor_to_pil(self, tensor: torch.Tensor) -> Image.Image:
|
| 184 |
+
tensor = tensor.detach().clone().cpu().clamp(0, 1).squeeze(0)
|
| 185 |
+
tensor = adjust_contrast(tensor, 1.5)
|
| 186 |
+
return self.to_pil(tensor)
|
| 187 |
+
|
| 188 |
+
def _augment_crops(self, target: torch.Tensor) -> torch.Tensor:
|
| 189 |
+
crops = []
|
| 190 |
+
for _ in range(NUM_CROPS):
|
| 191 |
+
crop = self.crop_transform(target)
|
| 192 |
+
crop = self.augment_transform(crop)
|
| 193 |
+
crops.append(crop)
|
| 194 |
+
return torch.cat(crops, dim=0)
|
| 195 |
+
|
| 196 |
+
@torch.no_grad()
|
| 197 |
+
def _encode_text(self, text: str) -> torch.Tensor:
|
| 198 |
+
prompts = prompt_ensemble(text)
|
| 199 |
+
tokens = self.tokenizer(prompts, padding=True, return_tensors="pt").to(
|
| 200 |
+
self.device
|
| 201 |
+
)
|
| 202 |
+
text_features = self.clip_model.get_text_features(**tokens)
|
| 203 |
+
text_features = text_features.mean(dim=0, keepdim=True)
|
| 204 |
+
text_features /= text_features.norm(dim=-1, keepdim=True)
|
| 205 |
+
return text_features
|
| 206 |
+
|
| 207 |
+
@torch.no_grad()
|
| 208 |
+
def _image_features(self, tensor: torch.Tensor) -> torch.Tensor:
|
| 209 |
+
feats = self.clip_model.get_image_features(pixel_values=clip_normalize(tensor))
|
| 210 |
+
feats /= feats.norm(dim=-1, keepdim=True)
|
| 211 |
+
return feats
|
| 212 |
+
|
cnn1.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class FiLMLayer(nn.Module):
|
| 6 |
+
"""
|
| 7 |
+
Feature-wise linear modulation module that conditions convolutional activations
|
| 8 |
+
on an external style embedding (e.g., a CLIP text embedding).
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
def __init__(self, num_channels: int, cond_dim: int, hidden_dim: int = 256):
|
| 12 |
+
super().__init__()
|
| 13 |
+
self.net = nn.Sequential(
|
| 14 |
+
nn.LayerNorm(cond_dim),
|
| 15 |
+
nn.Linear(cond_dim, hidden_dim),
|
| 16 |
+
nn.GELU(),
|
| 17 |
+
nn.Linear(hidden_dim, num_channels * 2),
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
def forward(self, x: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
|
| 21 |
+
gamma, beta = self.net(cond).chunk(2, dim=1)
|
| 22 |
+
gamma = gamma.unsqueeze(-1).unsqueeze(-1)
|
| 23 |
+
beta = beta.unsqueeze(-1).unsqueeze(-1)
|
| 24 |
+
return x * (1 + gamma) + beta
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class DoubleConv(nn.Module):
|
| 28 |
+
"""Two consecutive conv-batchnorm-gelu blocks with optional FiLM conditioning."""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
in_channels: int,
|
| 33 |
+
out_channels: int,
|
| 34 |
+
cond_dim: int | None = None,
|
| 35 |
+
film_hidden_dim: int = 256,
|
| 36 |
+
):
|
| 37 |
+
super().__init__()
|
| 38 |
+
self.conv = nn.Sequential(
|
| 39 |
+
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
|
| 40 |
+
nn.BatchNorm2d(out_channels),
|
| 41 |
+
nn.GELU(),
|
| 42 |
+
nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
|
| 43 |
+
nn.BatchNorm2d(out_channels),
|
| 44 |
+
nn.GELU(),
|
| 45 |
+
)
|
| 46 |
+
self.film = (
|
| 47 |
+
FiLMLayer(out_channels, cond_dim, hidden_dim=film_hidden_dim)
|
| 48 |
+
if cond_dim is not None
|
| 49 |
+
else None
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
def forward(self, x: torch.Tensor, cond: torch.Tensor | None = None) -> torch.Tensor:
|
| 53 |
+
x = self.conv(x)
|
| 54 |
+
if self.film is not None:
|
| 55 |
+
if cond is None:
|
| 56 |
+
raise ValueError("Style embedding is required for FiLM conditioning.")
|
| 57 |
+
x = self.film(x, cond)
|
| 58 |
+
return x
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class DownBlock(nn.Module):
|
| 62 |
+
"""Down-sampling block used in the encoder path."""
|
| 63 |
+
|
| 64 |
+
def __init__(
|
| 65 |
+
self,
|
| 66 |
+
in_channels: int,
|
| 67 |
+
out_channels: int,
|
| 68 |
+
cond_dim: int | None = None,
|
| 69 |
+
film_hidden_dim: int = 256,
|
| 70 |
+
):
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
|
| 73 |
+
self.conv = DoubleConv(
|
| 74 |
+
in_channels, out_channels, cond_dim, film_hidden_dim=film_hidden_dim
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
def forward(self, x: torch.Tensor, cond: torch.Tensor | None = None) -> torch.Tensor:
|
| 78 |
+
x = self.pool(x)
|
| 79 |
+
return self.conv(x, cond)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class UpBlock(nn.Module):
|
| 83 |
+
"""Up-sampling block with skip connections from the encoder path."""
|
| 84 |
+
|
| 85 |
+
def __init__(
|
| 86 |
+
self,
|
| 87 |
+
in_channels: int,
|
| 88 |
+
skip_channels: int,
|
| 89 |
+
cond_dim: int | None = None,
|
| 90 |
+
bilinear: bool = True,
|
| 91 |
+
film_hidden_dim: int = 256,
|
| 92 |
+
):
|
| 93 |
+
super().__init__()
|
| 94 |
+
if bilinear:
|
| 95 |
+
self.up = nn.Sequential(
|
| 96 |
+
nn.Upsample(scale_factor=2, mode="bilinear", align_corners=True),
|
| 97 |
+
nn.Conv2d(in_channels, in_channels // 2, kernel_size=1),
|
| 98 |
+
)
|
| 99 |
+
else:
|
| 100 |
+
self.up = nn.ConvTranspose2d(
|
| 101 |
+
in_channels, in_channels // 2, kernel_size=2, stride=2
|
| 102 |
+
)
|
| 103 |
+
self.conv = DoubleConv(
|
| 104 |
+
in_channels // 2 + skip_channels,
|
| 105 |
+
skip_channels,
|
| 106 |
+
cond_dim,
|
| 107 |
+
film_hidden_dim=film_hidden_dim,
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
def forward(
|
| 111 |
+
self, x: torch.Tensor, skip: torch.Tensor, cond: torch.Tensor | None = None
|
| 112 |
+
) -> torch.Tensor:
|
| 113 |
+
x = self.up(x)
|
| 114 |
+
diff_y = skip.size(2) - x.size(2)
|
| 115 |
+
diff_x = skip.size(3) - x.size(3)
|
| 116 |
+
if diff_y != 0 or diff_x != 0:
|
| 117 |
+
x = nn.functional.pad(
|
| 118 |
+
x,
|
| 119 |
+
[
|
| 120 |
+
diff_x // 2,
|
| 121 |
+
diff_x - diff_x // 2,
|
| 122 |
+
diff_y // 2,
|
| 123 |
+
diff_y - diff_y // 2,
|
| 124 |
+
],
|
| 125 |
+
)
|
| 126 |
+
x = torch.cat([skip, x], dim=1)
|
| 127 |
+
return self.conv(x, cond)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class OutConv(nn.Module):
|
| 131 |
+
"""Final projection into the RGB space."""
|
| 132 |
+
|
| 133 |
+
def __init__(self, in_channels: int, out_channels: int):
|
| 134 |
+
super().__init__()
|
| 135 |
+
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
|
| 136 |
+
|
| 137 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 138 |
+
return self.conv(x)
|
| 139 |
+
|
| 140 |
+
class UNet(nn.Module):
|
| 141 |
+
"""
|
| 142 |
+
Lightweight encoder-decoder network for CLIP-guided, text-prompted style transfer.
|
| 143 |
+
|
| 144 |
+
The network takes a content image and optionally a CLIP text embedding that
|
| 145 |
+
modulates intermediate activations through FiLM layers so that the decoded
|
| 146 |
+
image aligns with the target style semantics in CLIP space.
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
def __init__(
|
| 150 |
+
self,
|
| 151 |
+
in_channels: int = 3,
|
| 152 |
+
out_channels: int = 3,
|
| 153 |
+
base_channels: int = 16,
|
| 154 |
+
num_layers: int = 4,
|
| 155 |
+
text_dim: int = None,
|
| 156 |
+
bilinear: bool = True,
|
| 157 |
+
film_hidden_dim: int = 256,
|
| 158 |
+
):
|
| 159 |
+
super().__init__()
|
| 160 |
+
|
| 161 |
+
if num_layers < 2:
|
| 162 |
+
raise ValueError("num_layers must be >= 2")
|
| 163 |
+
|
| 164 |
+
self.cond_dim = text_dim
|
| 165 |
+
self.style_mapper = (
|
| 166 |
+
nn.Sequential(
|
| 167 |
+
nn.LayerNorm(text_dim),
|
| 168 |
+
nn.Linear(text_dim, text_dim),
|
| 169 |
+
nn.GELU(),
|
| 170 |
+
nn.Linear(text_dim, text_dim),
|
| 171 |
+
)
|
| 172 |
+
if text_dim is not None
|
| 173 |
+
else None
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
channels = [base_channels * (2**i) for i in range(num_layers)]
|
| 177 |
+
|
| 178 |
+
self.inc = DoubleConv(
|
| 179 |
+
in_channels,
|
| 180 |
+
channels[0],
|
| 181 |
+
self.cond_dim,
|
| 182 |
+
film_hidden_dim=film_hidden_dim,
|
| 183 |
+
)
|
| 184 |
+
self.downs = nn.ModuleList()
|
| 185 |
+
for idx in range(num_layers - 1):
|
| 186 |
+
self.downs.append(
|
| 187 |
+
DownBlock(
|
| 188 |
+
channels[idx],
|
| 189 |
+
channels[idx + 1],
|
| 190 |
+
self.cond_dim,
|
| 191 |
+
film_hidden_dim=film_hidden_dim,
|
| 192 |
+
)
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
self.bottleneck = DoubleConv(
|
| 196 |
+
channels[-1],
|
| 197 |
+
channels[-1] * 2,
|
| 198 |
+
self.cond_dim,
|
| 199 |
+
film_hidden_dim=film_hidden_dim,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
self.ups = nn.ModuleList()
|
| 203 |
+
prev_channels = channels[-1] * 2
|
| 204 |
+
for skip_ch in reversed(channels):
|
| 205 |
+
self.ups.append(
|
| 206 |
+
UpBlock(
|
| 207 |
+
prev_channels,
|
| 208 |
+
skip_ch,
|
| 209 |
+
self.cond_dim,
|
| 210 |
+
bilinear=bilinear,
|
| 211 |
+
film_hidden_dim=film_hidden_dim,
|
| 212 |
+
)
|
| 213 |
+
)
|
| 214 |
+
prev_channels = skip_ch
|
| 215 |
+
|
| 216 |
+
self.outc = OutConv(channels[0], out_channels)
|
| 217 |
+
self.activation = nn.Tanh()
|
| 218 |
+
|
| 219 |
+
def _prepare_condition(self, text_embedding: torch.Tensor | None) -> torch.Tensor | None:
|
| 220 |
+
if self.cond_dim is None:
|
| 221 |
+
return None
|
| 222 |
+
if text_embedding is None:
|
| 223 |
+
raise ValueError(
|
| 224 |
+
"text_embedding must be provided when the model is configured for conditioning."
|
| 225 |
+
)
|
| 226 |
+
if text_embedding.dim() != 2 or text_embedding.size(1) != self.cond_dim:
|
| 227 |
+
raise ValueError(
|
| 228 |
+
f"text_embedding must have shape [batch, {self.cond_dim}] but got {text_embedding.shape}."
|
| 229 |
+
)
|
| 230 |
+
return self.style_mapper(text_embedding) if self.style_mapper else text_embedding
|
| 231 |
+
|
| 232 |
+
def forward(
|
| 233 |
+
self, x: torch.Tensor, text_embedding: torch.Tensor | None = None
|
| 234 |
+
) -> torch.Tensor:
|
| 235 |
+
cond = self._prepare_condition(text_embedding)
|
| 236 |
+
|
| 237 |
+
skip_connections = []
|
| 238 |
+
x = self.inc(x, cond)
|
| 239 |
+
skip_connections.append(x)
|
| 240 |
+
|
| 241 |
+
for down in self.downs:
|
| 242 |
+
x = down(x, cond)
|
| 243 |
+
skip_connections.append(x)
|
| 244 |
+
|
| 245 |
+
x = self.bottleneck(x, cond)
|
| 246 |
+
|
| 247 |
+
for up, skip in zip(self.ups, reversed(skip_connections)):
|
| 248 |
+
x = up(x, skip, cond)
|
| 249 |
+
|
| 250 |
+
x = self.outc(x)
|
| 251 |
+
return self.activation(x)
|
requirements.txt
CHANGED
|
@@ -5,4 +5,5 @@ regex
|
|
| 5 |
tqdm
|
| 6 |
deeplake<4
|
| 7 |
gradio
|
| 8 |
-
git+https://github.com/openai/CLIP.git
|
|
|
|
|
|
| 5 |
tqdm
|
| 6 |
deeplake<4
|
| 7 |
gradio
|
| 8 |
+
git+https://github.com/openai/CLIP.git
|
| 9 |
+
transformers
|
style_net.py
CHANGED
|
@@ -1,63 +1,63 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
|
| 4 |
-
class ResidualBlock(nn.Module):
|
| 5 |
-
def __init__(self, channels):
|
| 6 |
-
super(ResidualBlock, self).__init__()
|
| 7 |
-
self.conv = nn.Sequential(
|
| 8 |
-
nn.Conv2d(channels, channels, 3, padding=1),
|
| 9 |
-
nn.InstanceNorm2d(channels),
|
| 10 |
-
nn.ReLU(),
|
| 11 |
-
nn.Conv2d(channels, channels, 3, padding=1),
|
| 12 |
-
nn.InstanceNorm2d(channels)
|
| 13 |
-
)
|
| 14 |
-
|
| 15 |
-
def forward(self, x):
|
| 16 |
-
return x + self.conv(x)
|
| 17 |
-
|
| 18 |
-
class UpsampleConvLayer(nn.Module):
|
| 19 |
-
def __init__(self, in_channels, out_channels, kernel_size, stride):
|
| 20 |
-
super().__init__()
|
| 21 |
-
self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
|
| 22 |
-
# Reflection padding reduces "border" artifacts
|
| 23 |
-
self.reflection_pad = nn.ReflectionPad2d(kernel_size // 2)
|
| 24 |
-
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=1)
|
| 25 |
-
|
| 26 |
-
def forward(self, x):
|
| 27 |
-
x = self.upsample(x)
|
| 28 |
-
x = self.reflection_pad(x)
|
| 29 |
-
return self.conv(x)
|
| 30 |
-
|
| 31 |
-
class StyleNet(nn.Module):
|
| 32 |
-
def __init__(self):
|
| 33 |
-
super(StyleNet, self).__init__()
|
| 34 |
-
# Encoder
|
| 35 |
-
self.encoder = nn.Sequential(
|
| 36 |
-
nn.ReflectionPad2d(4), # Pad first...
|
| 37 |
-
nn.Conv2d(3, 32, 9, padding=0), # ...then Conv (padding=0)
|
| 38 |
-
nn.InstanceNorm2d(32), nn.ReLU(),
|
| 39 |
-
|
| 40 |
-
nn.ReflectionPad2d(1),
|
| 41 |
-
nn.Conv2d(32, 64, 3, stride=2, padding=0),
|
| 42 |
-
nn.InstanceNorm2d(64), nn.ReLU(),
|
| 43 |
-
|
| 44 |
-
nn.ReflectionPad2d(1),
|
| 45 |
-
nn.Conv2d(64, 128, 3, stride=2, padding=0),
|
| 46 |
-
nn.InstanceNorm2d(128), nn.ReLU()
|
| 47 |
-
)
|
| 48 |
-
# Bottleneck
|
| 49 |
-
self.bottleneck = nn.Sequential(*[ResidualBlock(128) for _ in range(3)])
|
| 50 |
-
# Decoder
|
| 51 |
-
self.decoder = nn.Sequential(
|
| 52 |
-
UpsampleConvLayer(128, 64, kernel_size=3, stride=1),
|
| 53 |
-
nn.InstanceNorm2d(64), nn.ReLU(),
|
| 54 |
-
UpsampleConvLayer(64, 32, kernel_size=3, stride=1),
|
| 55 |
-
nn.InstanceNorm2d(32), nn.ReLU(),
|
| 56 |
-
nn.Conv2d(32, 3, 9, padding=4),
|
| 57 |
-
nn.Sigmoid()
|
| 58 |
-
)
|
| 59 |
-
|
| 60 |
-
def forward(self, x):
|
| 61 |
-
features = self.encoder(x)
|
| 62 |
-
features = self.bottleneck(features)
|
| 63 |
return self.decoder(features)
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
class ResidualBlock(nn.Module):
|
| 5 |
+
def __init__(self, channels):
|
| 6 |
+
super(ResidualBlock, self).__init__()
|
| 7 |
+
self.conv = nn.Sequential(
|
| 8 |
+
nn.Conv2d(channels, channels, 3, padding=1),
|
| 9 |
+
nn.InstanceNorm2d(channels),
|
| 10 |
+
nn.ReLU(),
|
| 11 |
+
nn.Conv2d(channels, channels, 3, padding=1),
|
| 12 |
+
nn.InstanceNorm2d(channels)
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
def forward(self, x):
|
| 16 |
+
return x + self.conv(x)
|
| 17 |
+
|
| 18 |
+
class UpsampleConvLayer(nn.Module):
|
| 19 |
+
def __init__(self, in_channels, out_channels, kernel_size, stride):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
|
| 22 |
+
# Reflection padding reduces "border" artifacts
|
| 23 |
+
self.reflection_pad = nn.ReflectionPad2d(kernel_size // 2)
|
| 24 |
+
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=1)
|
| 25 |
+
|
| 26 |
+
def forward(self, x):
|
| 27 |
+
x = self.upsample(x)
|
| 28 |
+
x = self.reflection_pad(x)
|
| 29 |
+
return self.conv(x)
|
| 30 |
+
|
| 31 |
+
class StyleNet(nn.Module):
|
| 32 |
+
def __init__(self):
|
| 33 |
+
super(StyleNet, self).__init__()
|
| 34 |
+
# Encoder
|
| 35 |
+
self.encoder = nn.Sequential(
|
| 36 |
+
nn.ReflectionPad2d(4), # Pad first...
|
| 37 |
+
nn.Conv2d(3, 32, 9, padding=0), # ...then Conv (padding=0)
|
| 38 |
+
nn.InstanceNorm2d(32), nn.ReLU(),
|
| 39 |
+
|
| 40 |
+
nn.ReflectionPad2d(1),
|
| 41 |
+
nn.Conv2d(32, 64, 3, stride=2, padding=0),
|
| 42 |
+
nn.InstanceNorm2d(64), nn.ReLU(),
|
| 43 |
+
|
| 44 |
+
nn.ReflectionPad2d(1),
|
| 45 |
+
nn.Conv2d(64, 128, 3, stride=2, padding=0),
|
| 46 |
+
nn.InstanceNorm2d(128), nn.ReLU()
|
| 47 |
+
)
|
| 48 |
+
# Bottleneck
|
| 49 |
+
self.bottleneck = nn.Sequential(*[ResidualBlock(128) for _ in range(3)])
|
| 50 |
+
# Decoder
|
| 51 |
+
self.decoder = nn.Sequential(
|
| 52 |
+
UpsampleConvLayer(128, 64, kernel_size=3, stride=1),
|
| 53 |
+
nn.InstanceNorm2d(64), nn.ReLU(),
|
| 54 |
+
UpsampleConvLayer(64, 32, kernel_size=3, stride=1),
|
| 55 |
+
nn.InstanceNorm2d(32), nn.ReLU(),
|
| 56 |
+
nn.Conv2d(32, 3, 9, padding=4),
|
| 57 |
+
nn.Sigmoid()
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
def forward(self, x):
|
| 61 |
+
features = self.encoder(x)
|
| 62 |
+
features = self.bottleneck(features)
|
| 63 |
return self.decoder(features)
|
util.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PIL import Image
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.optim as optim
|
| 7 |
+
from torchvision import transforms, models
|
| 8 |
+
|
| 9 |
+
IMG_MEAN = [0.485, 0.456, 0.406]
|
| 10 |
+
IMG_STD = [0.229, 0.224, 0.225]
|
| 11 |
+
CLIP_MEAN = [0.48145466, 0.4578275, 0.40821073]
|
| 12 |
+
CLIP_STD = [0.26862954, 0.26130258, 0.27577711]
|
| 13 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 14 |
+
|
| 15 |
+
LR = 5e-4
|
| 16 |
+
CROP_SIZE = 128
|
| 17 |
+
RESIZE = 224
|
| 18 |
+
NUM_EPOCHS = 200
|
| 19 |
+
NUM_CROPS = 64
|
| 20 |
+
PATCH_THRESHOLD = 0.7
|
| 21 |
+
L_TV = 2e-3
|
| 22 |
+
L_PATCH = 9000
|
| 23 |
+
L_DIR = 500
|
| 24 |
+
L_CONTENT = 150
|
| 25 |
+
IMG_SIZE = 512
|
| 26 |
+
# copied from openai clip
|
| 27 |
+
IMAGENET_TEMPLATES = [
|
| 28 |
+
"a bad photo of a {}.",
|
| 29 |
+
"a photo of many {}.",
|
| 30 |
+
"a sculpture of a {}.",
|
| 31 |
+
"a photo of the hard to see {}.",
|
| 32 |
+
"a low resolution photo of the {}.",
|
| 33 |
+
"a rendering of a {}.",
|
| 34 |
+
"graffiti of a {}.",
|
| 35 |
+
"a bad photo of the {}.",
|
| 36 |
+
"a cropped photo of the {}.",
|
| 37 |
+
"a tattoo of a {}.",
|
| 38 |
+
"the embroidered {}.",
|
| 39 |
+
"a photo of a hard to see {}.",
|
| 40 |
+
"a bright photo of a {}.",
|
| 41 |
+
"a photo of a clean {}.",
|
| 42 |
+
"a photo of a dirty {}.",
|
| 43 |
+
"a dark photo of the {}.",
|
| 44 |
+
"a drawing of a {}.",
|
| 45 |
+
"a photo of my {}.",
|
| 46 |
+
"the plastic {}.",
|
| 47 |
+
"a photo of the cool {}.",
|
| 48 |
+
"a close-up photo of a {}.",
|
| 49 |
+
"a black and white photo of the {}.",
|
| 50 |
+
"a painting of the {}.",
|
| 51 |
+
"a painting of a {}.",
|
| 52 |
+
"a pixelated photo of the {}.",
|
| 53 |
+
"a sculpture of the {}.",
|
| 54 |
+
"a bright photo of the {}.",
|
| 55 |
+
"a cropped photo of a {}.",
|
| 56 |
+
"a plastic {}.",
|
| 57 |
+
"a photo of the dirty {}.",
|
| 58 |
+
"a jpeg corrupted photo of a {}.",
|
| 59 |
+
"a blurry photo of the {}.",
|
| 60 |
+
"a photo of the {}.",
|
| 61 |
+
"a good photo of the {}.",
|
| 62 |
+
"a rendering of the {}.",
|
| 63 |
+
"a {} in a video game.",
|
| 64 |
+
"a photo of one {}.",
|
| 65 |
+
"a doodle of a {}.",
|
| 66 |
+
"a close-up photo of the {}.",
|
| 67 |
+
"a photo of a {}.",
|
| 68 |
+
"the origami {}.",
|
| 69 |
+
"the {} in a video game.",
|
| 70 |
+
"a sketch of a {}.",
|
| 71 |
+
"a doodle of the {}.",
|
| 72 |
+
"a origami {}.",
|
| 73 |
+
"a low resolution photo of a {}.",
|
| 74 |
+
"the toy {}.",
|
| 75 |
+
"a rendition of the {}.",
|
| 76 |
+
"a photo of the clean {}.",
|
| 77 |
+
"a photo of a large {}.",
|
| 78 |
+
"a rendition of a {}.",
|
| 79 |
+
"a photo of a nice {}.",
|
| 80 |
+
"a photo of a weird {}.",
|
| 81 |
+
"a blurry photo of a {}.",
|
| 82 |
+
"a cartoon {}.",
|
| 83 |
+
"art of a {}.",
|
| 84 |
+
"a sketch of the {}.",
|
| 85 |
+
"a embroidered {}.",
|
| 86 |
+
"a pixelated photo of a {}.",
|
| 87 |
+
"itap of the {}.",
|
| 88 |
+
"a jpeg corrupted photo of the {}.",
|
| 89 |
+
"a good photo of a {}.",
|
| 90 |
+
"a plushie {}.",
|
| 91 |
+
"a photo of the nice {}.",
|
| 92 |
+
"a photo of the small {}.",
|
| 93 |
+
"a photo of the weird {}.",
|
| 94 |
+
"the cartoon {}.",
|
| 95 |
+
"art of the {}.",
|
| 96 |
+
"a drawing of the {}.",
|
| 97 |
+
"a photo of the large {}.",
|
| 98 |
+
"a black and white photo of a {}.",
|
| 99 |
+
"the plushie {}.",
|
| 100 |
+
"a dark photo of a {}.",
|
| 101 |
+
"itap of a {}.",
|
| 102 |
+
"graffiti of the {}.",
|
| 103 |
+
"a toy {}.",
|
| 104 |
+
"itap of my {}.",
|
| 105 |
+
"a photo of a cool {}.",
|
| 106 |
+
"a photo of a small {}.",
|
| 107 |
+
"a tattoo of the {}.",
|
| 108 |
+
]
|
| 109 |
+
|
| 110 |
+
def get_mean(mean_dist):
|
| 111 |
+
mean = torch.tensor(mean_dist).to(DEVICE)
|
| 112 |
+
return mean.view(1, -1, 1, 1)
|
| 113 |
+
|
| 114 |
+
def get_std(std_dist):
|
| 115 |
+
std = torch.tensor(std_dist).to(DEVICE)
|
| 116 |
+
return std.view(1, -1, 1, 1)
|
| 117 |
+
|
| 118 |
+
def normalize(data):
|
| 119 |
+
mean = get_mean(IMG_MEAN)
|
| 120 |
+
std = get_std(IMG_STD)
|
| 121 |
+
|
| 122 |
+
norm_data = (data - mean) / std
|
| 123 |
+
return norm_data
|
| 124 |
+
|
| 125 |
+
def clip_normalize(data):
|
| 126 |
+
resized = nn.functional.interpolate(data, size=RESIZE, mode='bicubic')
|
| 127 |
+
mean = get_mean(CLIP_MEAN)
|
| 128 |
+
std = get_std(CLIP_STD)
|
| 129 |
+
norm_data = (resized - mean) / std
|
| 130 |
+
return norm_data
|
| 131 |
+
|
| 132 |
+
def load_image(img_path):
|
| 133 |
+
image = Image.open(img_path)
|
| 134 |
+
image = image.resize((IMG_SIZE, IMG_SIZE))
|
| 135 |
+
transform = transforms.Compose([transforms.ToTensor()])
|
| 136 |
+
return transform(image)[:3, :, :].unsqueeze(0)
|
| 137 |
+
|
| 138 |
+
def get_features(image, vgg19):
|
| 139 |
+
# uses vgg19 model to extract content features
|
| 140 |
+
layers = {'0': 'conv1_1',
|
| 141 |
+
'5': 'conv2_1',
|
| 142 |
+
'10': 'conv3_1',
|
| 143 |
+
'19': 'conv4_1',
|
| 144 |
+
'21': 'conv4_2',
|
| 145 |
+
'28': 'conv5_1',
|
| 146 |
+
'31': 'conv5_2'
|
| 147 |
+
}
|
| 148 |
+
features = {}
|
| 149 |
+
x = image
|
| 150 |
+
for name, layer in vgg19._modules.items():
|
| 151 |
+
x = layer(x)
|
| 152 |
+
if name in layers:
|
| 153 |
+
features[layers[name]] = x
|
| 154 |
+
|
| 155 |
+
return features
|
| 156 |
+
|
| 157 |
+
def prompt_ensemble(prompt):
|
| 158 |
+
return [template.format(prompt) for template in IMAGENET_TEMPLATES]
|
| 159 |
+
|
| 160 |
+
def get_image_prior_losses(target):
|
| 161 |
+
diff1 = target[:, :, :, :-1] - target[:, :, :, 1:]
|
| 162 |
+
diff2 = target[:, :, :-1, :] - target[:, :, 1:, :]
|
| 163 |
+
diff3 = target[:, :, 1:, :-1] - target[:, :, :-1, 1:]
|
| 164 |
+
diff4 = target[:, :, :-1, :-1] - target[:, :, 1:, 1:]
|
| 165 |
+
|
| 166 |
+
loss_var_l2 = torch.norm(diff1) + torch.norm(diff2) + torch.norm(diff3) + torch.norm(diff4)
|
| 167 |
+
|
| 168 |
+
return loss_var_l2
|