randomoneguy commited on
Commit
5a14c00
·
verified ·
1 Parent(s): 98b6c0d
Files changed (6) hide show
  1. app.py +39 -28
  2. clip_styler.py +212 -0
  3. cnn1.py +251 -0
  4. requirements.txt +2 -1
  5. style_net.py +62 -62
  6. 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. Editing Model
18
- style_net = StyleNet().to(DEVICE)
19
 
20
- # B. Retrieval Model (CLIP)
21
- clip_model, preprocess = clip.load("ViT-B/32", device=DEVICE)
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: return None
71
-
72
- weights_map = {
73
- "Sketch": "model_sketch.pth",
74
- "Van Gogh": "model_painting.pth",
75
- "Cyberpunk": "model_cyberpunk.pth"
76
- }
77
-
78
  try:
79
- style_net.load_state_dict(torch.load(weights_map.get(style_choice), map_location=DEVICE))
80
- except Exception:
 
 
 
 
 
 
 
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