rusticolus commited on
Commit
442d9ae
·
verified ·
1 Parent(s): f5e118e

Initial deployment

Browse files
app.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ 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...")
25
+ try:
26
+ image_features = torch.load(INDEX_PATH).to(DEVICE)
27
+ print(f"Index loaded: {image_features.shape[0]} images.")
28
+ except FileNotFoundError:
29
+ print("WARNING: 'flickr_embeddings.pt' not found. Run indexer.py first!")
30
+ image_features = None
31
+
32
+ # 3. CONNECT TO DATASET (For fetching the actual images)
33
+ # Note: No transform here because we want to display the original PIL image
34
+ dataset_raw = FlickrStreamer(limit=SEARCH_LIMIT, transform=None)
35
+
36
+ # --- FUNCTION: SEARCH ---
37
+ def search_flickr(text_query):
38
+ if image_features is None:
39
+ return []
40
+
41
+ with torch.no_grad():
42
+ # 1. Encode Text
43
+ text_tokens = clip.tokenize([text_query]).to(DEVICE)
44
+ text_features = clip_model.encode_text(text_tokens)
45
+ text_features /= text_features.norm(dim=-1, keepdim=True)
46
+
47
+ # 2. Math: Cosine Similarity (Dot Product)
48
+ # (1, 512) x (5000, 512).T -> (1, 5000) similarity scores
49
+ similarity = (100.0 * text_features @ image_features.T).softmax(dim=-1)
50
+
51
+ # 3. Get Top 5 matches
52
+ values, indices = similarity[0].topk(5)
53
+
54
+ # 4. Retrieve actual images from dataset
55
+ # (Deep Lake might be slightly slow here as it fetches specific indices)
56
+ results = []
57
+ for idx in indices:
58
+ img_idx = idx.item()
59
+ results.append(dataset_raw[img_idx])
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:
92
+ gr.Markdown("# Group 19: CLIP Search & Edit Engine")
93
+
94
+ with gr.Tab("Retrieval"):
95
+ gr.Markdown(f"Search through {SEARCH_LIMIT} Flickr images by concept.")
96
+ with gr.Row():
97
+ txt_query = gr.Textbox(label="Type a query (e.g., 'a dog on a boat', 'neon lights')")
98
+ btn_search = gr.Button("Search Library")
99
+
100
+ # Output is a Gallery (Grid of images)
101
+ gallery_out = gr.Gallery(label="Top Results", columns=5, height=300)
102
+
103
+ btn_search.click(search_flickr, inputs=txt_query, outputs=gallery_out)
104
+
105
+ with gr.Tab("Image Editing"):
106
+ gr.Markdown("Transform any image using our trained CLIPstyler models.")
107
+ with gr.Row():
108
+ im_in = gr.Image(type="pil", label="Input Image")
109
+ style = gr.Dropdown(["Sketch", "Van Gogh", "Cyberpunk"], label="Choose Style")
110
+
111
+ btn_edit = gr.Button("Apply")
112
+ im_out = gr.Image(label="Result")
113
+
114
+ btn_edit.click(edit_image, inputs=[im_in, style], outputs=im_out)
115
+
116
+ if __name__ == "__main__":
117
+ demo.launch(share=True)
dataset.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import deeplake
2
+ import torch
3
+ from PIL import Image
4
+ import numpy as np
5
+ from torch.utils.data import Dataset
6
+
7
+ class FlickrStreamer(Dataset):
8
+ def __init__(self, limit=1000, transform=None):
9
+ # Streams data from cloud. No massive download needed.
10
+ print(f"Connecting to Flickr30k (First {limit} images)...")
11
+ # Windows sometimes has permission issues with cache, so we explicitly set access_method
12
+ self.ds = deeplake.load('hub://activeloop/flickr30k', access_method="stream")
13
+ self.limit = limit
14
+ self.transform = transform
15
+
16
+ def __len__(self):
17
+ return self.limit
18
+
19
+ def __getitem__(self, idx):
20
+ sample = self.ds[idx]
21
+ image_data = sample.image.numpy()
22
+
23
+ try:
24
+ image = Image.fromarray(image_data)
25
+ except:
26
+ image = Image.fromarray(image_data.astype('uint8'))
27
+
28
+ if self.transform:
29
+ image = self.transform(image)
30
+
31
+ return image
flickr_embeddings.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eacb3b63b79825e8bf08181475e1fa9077c41a7f956154528d5d0190d9332a97
3
+ size 5121230
model_cyberpunk.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a4ddbdb1b52659c40ff6ea117878f0713750e157847f4e1b1e8068c94e3e2933
3
+ size 4352550
model_painting.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:737cc250d87b0aca72ba773bd871712d4ed9255ee1bdcc130873186cb47ce4e6
3
+ size 4352522
model_sketch.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9bbbe4be7bf1a0cadad45a40b83a55160abc99235207bf77c70547caa59af68d
3
+ size 4352466
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ torchvision
3
+ ftfy
4
+ regex
5
+ tqdm
6
+ deeplake
7
+ gradio
8
+ git+https://github.com/openai/CLIP.git
style_net.py ADDED
@@ -0,0 +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)