Spaces:
Sleeping
Sleeping
File size: 11,051 Bytes
7f57474 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
import gradio as gr
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision.models as models
from PIL import Image
import numpy as np
import os
import requests
import base64
import io
# CPU optimization: Disable CUDA and use optimized CPU threads
torch.set_num_threads(4) # Adjust based on your CPU
device = torch.device("cpu")
# Get QuickCloud API URL from environment variable
QUICKCLOUD_API_URL = os.environ.get("QUICKCLOUD_API_URL", "")
class LightingStyleTransfer:
def __init__(self):
# Use VGG16 for feature extraction (lighter than VGG19)
vgg = models.vgg16(pretrained=True).features.to(device).eval()
# Freeze parameters for CPU efficiency
for param in vgg.parameters():
param.requires_grad = False
self.model = vgg
# Layer indices for content and style
self.style_layers = [0, 5, 10, 17] # Reduced layers for CPU
self.content_layers = [17]
def preprocess(self, img, max_size=512):
"""Resize and normalize image - smaller size for CPU"""
# CPU optimization: Use smaller image size
w, h = img.size
scale = max_size / max(w, h)
new_size = (int(w * scale), int(h * scale))
img = img.resize(new_size, Image.LANCZOS)
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
return transform(img).unsqueeze(0).to(device)
def deprocess(self, tensor):
"""Convert tensor back to image"""
img = tensor.cpu().clone().squeeze(0)
img = img.clamp(0, 1)
img = transforms.ToPILImage()(img)
return img
def gram_matrix(self, tensor):
"""Compute Gram matrix for style representation"""
b, c, h, w = tensor.size()
features = tensor.view(b * c, h * w)
G = torch.mm(features, features.t())
return G.div(b * c * h * w)
def get_features(self, image):
"""Extract features from specified layers"""
features = {}
x = image
for idx, layer in enumerate(self.model):
x = layer(x)
if idx in self.style_layers:
features[f'style_{idx}'] = x
if idx in self.content_layers:
features[f'content_{idx}'] = x
return features
def transfer(self, content_img, style_img, steps=150, style_weight=1e6,
content_weight=1):
"""Perform lighting style transfer"""
# Preprocess images
content = self.preprocess(content_img)
style = self.preprocess(style_img)
# Initialize target as content image
target = content.clone().requires_grad_(True)
# Get features
content_features = self.get_features(content)
style_features = self.get_features(style)
# Compute style gram matrices
style_grams = {k: self.gram_matrix(v) for k, v in style_features.items()
if 'style' in k}
# CPU optimization: Use LBFGS optimizer (faster convergence)
optimizer = torch.optim.LBFGS([target], max_iter=20)
step = [0]
def closure():
target.data.clamp_(0, 1)
optimizer.zero_grad()
target_features = self.get_features(target)
# Content loss
content_loss = 0
for k in content_features:
if 'content' in k:
content_loss += torch.mean((target_features[k] -
content_features[k]) ** 2)
# Style loss
style_loss = 0
for k in style_grams:
target_gram = self.gram_matrix(target_features[k])
style_loss += torch.mean((target_gram - style_grams[k]) ** 2)
# Total loss
total_loss = content_weight * content_loss + style_weight * style_loss
total_loss.backward()
step[0] += 1
if step[0] % 30 == 0:
print(f"Step {step[0]}, Loss: {total_loss.item():.2f}")
return total_loss
# Optimization loop
epochs = steps // 20 # LBFGS takes ~20 iterations per step
for i in range(epochs):
optimizer.step(closure)
if step[0] >= steps:
break
# Final clamp and return
target.data.clamp_(0, 1)
return self.deprocess(target)
def process_with_quickcloud(content_img, style_img, steps, style_strength):
"""Process using QuickCloud API (powered by Modal.com)"""
if not QUICKCLOUD_API_URL:
return None, "❌ QuickCloud API URL not configured. Please set QUICKCLOUD_API_URL environment variable."
try:
# Convert PIL images to bytes
content_bytes = io.BytesIO()
style_bytes = io.BytesIO()
content_img.save(content_bytes, format='PNG')
style_img.save(style_bytes, format='PNG')
# Encode to base64
content_b64 = base64.b64encode(content_bytes.getvalue()).decode()
style_b64 = base64.b64encode(style_bytes.getvalue()).decode()
# Prepare request
payload = {
"content_image": content_b64,
"style_image": style_b64,
"steps": steps,
"style_weight": style_strength * 1e6,
"content_weight": 1.0,
"learning_rate": 0.03
}
print("Sending request to NamelessAI QuickCloud (H100 GPU)...")
# Make API request
response = requests.post(QUICKCLOUD_API_URL, json=payload, timeout=300)
response.raise_for_status()
# Decode result
result_data = response.json()
result_bytes = base64.b64decode(result_data["result_image"])
result_img = Image.open(io.BytesIO(result_bytes))
return result_img, "✅ Processing complete via QuickCloud (H100 GPU)!"
except requests.exceptions.Timeout:
return None, "❌ Request timed out. Please try again."
except requests.exceptions.RequestException as e:
return None, f"❌ API Error: {str(e)}"
except Exception as e:
return None, f"❌ Error: {str(e)}"
def process_locally(content_img, style_img, steps, style_strength):
"""Process using local CPU"""
try:
# Adjust style weight
style_weight = style_strength * 1e6
# Perform transfer
result = style_transfer.transfer(
content_img,
style_img,
steps=steps,
style_weight=style_weight,
content_weight=1
)
return result, "✅ Processing complete via Local CPU!"
except Exception as e:
return None, f"❌ Error: {str(e)}"
def process_images(content_img, style_img, steps, style_strength, use_quickcloud):
"""Process the style transfer based on selected mode"""
if content_img is None or style_img is None:
return None, "⚠️ Please upload both content and style images."
# Convert to PIL if needed
if isinstance(content_img, np.ndarray):
content_img = Image.fromarray(content_img)
if isinstance(style_img, np.ndarray):
style_img = Image.fromarray(style_img)
if use_quickcloud:
return process_with_quickcloud(content_img, style_img, steps, style_strength)
else:
return process_locally(content_img, style_img, steps, style_strength)
# Initialize local model (done once at startup)
print("Loading local model... This may take a moment.")
style_transfer = LightingStyleTransfer()
print("Local model loaded successfully!")
# Check if QuickCloud is available
quickcloud_available = bool(QUICKCLOUD_API_URL)
if quickcloud_available:
print(f"✓ QuickCloud API configured and available")
else:
print("✗ QuickCloud API not configured (set QUICKCLOUD_API_URL environment variable)")
# Create Gradio interface
with gr.Blocks(title="AI Lighting Style Transfer") as demo:
gr.Markdown("""
# 🎨 AI-Powered Lighting Style Transfer
Transfer the lighting and color style from one image to another using neural style transfer.
## Processing Options:
- **Local (CPU)**: Runs on your machine. Takes 1-3 minutes. Free.
- **NamelessAI QuickCloud**: Runs on H100 GPU cloud. Takes 5-10 seconds. Requires API key.
- *Powered by Modal.com*
## How to use:
1. Upload your **content image** (the image you want to transform)
2. Upload your **style image** (the image whose lighting you want to copy)
3. Choose processing mode (Local or QuickCloud)
4. Adjust settings if desired
5. Click "Transfer Style" and wait for processing
""")
with gr.Row():
with gr.Column():
content_input = gr.Image(label="Content Image", type="pil")
style_input = gr.Image(label="Style Image", type="pil")
with gr.Column():
output = gr.Image(label="Result")
status_text = gr.Textbox(label="Status", interactive=False)
with gr.Row():
use_quickcloud = gr.Checkbox(
label="Use NamelessAI QuickCloud (H100 GPU - Powered by Modal.com)",
value=False,
interactive=quickcloud_available,
info="5-10 seconds vs 1-3 minutes locally" if quickcloud_available else "API URL not configured"
)
with gr.Row():
steps_slider = gr.Slider(
minimum=50,
maximum=300,
value=150,
step=10,
label="Optimization Steps (more = better quality, slower)"
)
style_strength = gr.Slider(
minimum=0.5,
maximum=3.0,
value=1.0,
step=0.1,
label="Style Strength"
)
transfer_btn = gr.Button("Transfer Style", variant="primary", size="lg")
gr.Markdown("""
### Tips:
- **Local Mode**: Images resized to 512px, use 100-150 steps for balance
- **QuickCloud Mode**: Handles 1024px images, 300 steps recommended for best quality
- Increase style strength for more dramatic lighting effects
- Works best with images that have distinct lighting patterns
### QuickCloud Setup:
To use QuickCloud, set the `QUICKCLOUD_API_URL` environment variable to your Modal API endpoint.
""")
# Set up the button click
transfer_btn.click(
fn=process_images,
inputs=[content_input, style_input, steps_slider, style_strength, use_quickcloud],
outputs=[output, status_text]
)
# Launch the app
if __name__ == "__main__":
demo.launch() |