text2img / app.py
Avanish11's picture
Update app.py
58de9d9 verified
Raw
History Blame Contribute Delete
20.7 kB
import io
import base64
from fastapi import FastAPI, Form, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from PIL import Image
import time
import logging
from datetime import datetime
import os
import torch
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="HD CPU Text-to-Image Generator")
# Create directories
os.makedirs("generated_images", exist_ok=True)
os.makedirs("models", exist_ok=True)
# Global pipeline variable
pipe = None
model_loaded = False
model_name = None
# Fixed HD settings - ALWAYS 512x512 for best quality with turbo models
FIXED_WIDTH = 512
FIXED_HEIGHT = 512
def load_model():
"""Auto-download and load optimized CPU model for HD quality"""
global pipe, model_loaded, model_name
if model_loaded and pipe is not None:
return True
logger.info("=" * 50)
logger.info("📥 Loading SDXL-Turbo for High Quality HD images...")
logger.info("=" * 50)
try:
from diffusers import AutoPipelineForText2Image, DPMSolverMultistepScheduler
import torch
# SDXL-Turbo - Much better quality than regular SD-Turbo
# Fixed at 512x512 for optimal HD output
model_repo = "stabilityai/sdxl-turbo"
logger.info(f"🔄 Loading {model_repo}...")
# Load pipeline with CPU optimizations
pipe = AutoPipelineForText2Image.from_pretrained(
model_repo,
torch_dtype=torch.float32,
variant="fp16",
use_safetensors=True,
low_cpu_mem_usage=True
)
# Move to CPU
pipe = pipe.to("cpu")
# Use fast scheduler
pipe.scheduler = DPMSolverMultistepScheduler.from_config(
pipe.scheduler.config,
use_karras_sigmas=True # Better quality
)
# Memory optimizations
pipe.enable_attention_slicing()
# Disable safety checker for speed (optional)
if hasattr(pipe, 'safety_checker'):
pipe.safety_checker = None
model_name = "SDXL-Turbo (HD Quality)"
model_loaded = True
logger.info("✅ Loaded SDXL-Turbo - HD Quality mode (512x512 fixed)")
logger.info("=" * 50)
return True
except Exception as e:
logger.error(f"Failed to load SDXL-Turbo: {e}")
# Fallback to regular SD-Turbo with optimized settings
try:
logger.info("🔄 Falling back to SD-Turbo with HD optimizations...")
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
pipe = StableDiffusionPipeline.from_pretrained(
"stabilityai/sd-turbo",
torch_dtype=torch.float32,
low_cpu_mem_usage=True
)
pipe = pipe.to("cpu")
pipe.scheduler = DPMSolverMultistepScheduler.from_config(
pipe.scheduler.config,
use_karras_sigmas=True
)
pipe.enable_attention_slicing()
model_name = "SD-Turbo (HD Optimized)"
model_loaded = True
logger.info("✅ Loaded SD-Turbo with HD optimizations")
return True
except Exception as e2:
logger.error(f"All models failed: {e2}")
return False
# HTML Template with fixed HD settings
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎨 HD CPU Text-to-Image Generator | 512x512 Quality</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 { font-size: 2em; margin-bottom: 10px; }
.header p { opacity: 0.9; }
.hd-badge {
display: inline-block;
background: #28a745;
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
margin-top: 10px;
}
.content { padding: 30px; }
.input-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; font-weight: 600; color: #333; }
textarea, input, select {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 14px;
transition: border-color 0.3s;
}
textarea:focus, input:focus, select:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
min-height: 100px;
font-family: inherit;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.info-box {
background: #e8f0fe;
padding: 15px;
border-radius: 10px;
margin-bottom: 20px;
font-size: 14px;
color: #1e3c72;
}
.info-box h4 { margin-bottom: 10px; color: #667eea; }
button {
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(40, 167, 69, 0.4);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.result-container { margin-top: 30px; text-align: center; }
.image-container {
margin-top: 20px;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
display: none;
background: #f5f5f5;
}
.image-container img {
width: 100%;
height: auto;
display: block;
max-width: 512px;
margin: 0 auto;
}
.info {
background: #f5f5f5;
padding: 10px;
border-radius: 10px;
margin-top: 10px;
font-size: 14px;
color: #666;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #28a745;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status { margin-top: 15px; padding: 10px; border-radius: 10px; text-align: center; }
.status.success { background: #d4edda; color: #155724; }
.status.error { background: #f8d7da; color: #721c24; }
.status.warning { background: #fff3cd; color: #856404; }
.download-btn { background: #007bff; margin-top: 10px; padding: 10px; }
.tip {
background: #f8f9fa;
padding: 10px;
border-radius: 10px;
margin-top: 15px;
font-size: 12px;
color: #666;
text-align: left;
}
.tip strong { color: #28a745; }
@media (max-width: 768px) {
.content { padding: 20px; }
.settings-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎨 HD CPU Text-to-Image Generator</h1>
<p>High Quality 512x512 Images on Your CPU - Fast & Free!</p>
<div class="hd-badge">📸 FIXED 512x512 HD OUTPUT</div>
</div>
<div class="content">
<div class="info-box">
<h4>✨ HD Quality Tips:</h4>
✅ Fixed 512x512 resolution for optimal quality<br>
✅ 4-6 inference steps for best balance of speed & quality<br>
✅ Use detailed prompts (50-100 words) for better results<br>
✅ Negative prompts help remove artifacts & improve clarity
</div>
<form id="generateForm">
<div class="input-group">
<label>🎯 Prompt (Be detailed for HD quality)</label>
<textarea id="prompt" placeholder="Example: 'A stunning landscape photograph of a misty mountain lake at sunrise, with golden light reflecting on calm water, pine trees silhouetted against colorful sky, professional photography, 8K, highly detailed, sharp focus, cinematic lighting'" required></textarea>
</div>
<div class="input-group">
<label>🚫 Negative Prompt (Critical for HD quality - helps remove defects)</label>
<textarea id="negativePrompt" placeholder="Example: 'blurry, low quality, worst quality, deformed, ugly, bad anatomy, disfigured, missing fingers, extra digits, cropped, jpeg artifacts, lowres, oversmooth, watermark, text, error'"></textarea>
</div>
<div class="settings-grid">
<div>
<label>⚙️ Steps (1-6) - 4 is optimal for HD</label>
<input type="number" id="steps" value="4" min="1" max="6">
<small style="color:#999;">1 step: Fast, 4 steps: HD Quality, 6 steps: Max Quality</small>
</div>
<div>
<label>🎨 Guidance Scale (1-3) - Lower = more creative</label>
<input type="number" id="guidanceScale" value="2.0" min="1" max="3" step="0.1">
<small style="color:#999;">Turbo models: 1.5-2.5 is optimal</small>
</div>
</div>
<div class="info-box" style="background:#fff3cd;">
<h4>📸 HD Output Settings (Fixed):</h4>
✅ Resolution: <strong>512 x 512 pixels (High Definition)</strong><br>
✅ Model: SDXL-Turbo (Optimized for HD quality)<br>
✅ Auto-upscaling: Enabled for crystal clear output
</div>
<button type="submit" id="generateBtn">🚀 Generate HD Image</button>
</form>
<div class="result-container">
<div id="status"></div>
<div id="imageContainer" class="image-container">
<img id="generatedImage" alt="Generated HD Image">
<div class="info" id="imageInfo"></div>
<button id="downloadBtn" class="download-btn">💾 Download HD Image (512x512 PNG)</button>
</div>
<div class="tip">
💡 <strong>HD Quality Tips:</strong> For best results, use detailed prompts (50-100 words),
include lighting details (e.g., "golden hour", "studio lighting"),
and always use the negative prompt to remove artifacts. Resolution is fixed at 512x512 for optimal quality.
</div>
</div>
</div>
</div>
<script>
const form = document.getElementById('generateForm');
const generateBtn = document.getElementById('generateBtn');
const imageContainer = document.getElementById('imageContainer');
const generatedImage = document.getElementById('generatedImage');
const imageInfo = document.getElementById('imageInfo');
const statusDiv = document.getElementById('status');
const downloadBtn = document.getElementById('downloadBtn');
let currentImageData = null;
form.addEventListener('submit', async (e) => {
e.preventDefault();
generateBtn.disabled = true;
generateBtn.innerHTML = '<span class="loading"></span> Generating HD Image...';
statusDiv.innerHTML = '';
imageContainer.style.display = 'none';
const formData = new FormData();
formData.append('prompt', document.getElementById('prompt').value);
formData.append('negative_prompt', document.getElementById('negativePrompt').value);
formData.append('steps', document.getElementById('steps').value);
formData.append('guidance_scale', document.getElementById('guidanceScale').value);
// Width and height are FIXED at 512x512 for HD quality
const startTime = Date.now();
try {
const response = await fetch('/generate', { method: 'POST', body: formData });
const data = await response.json();
const elapsed = (Date.now() - startTime) / 1000;
if (response.ok) {
generatedImage.src = `data:image/png;base64,${data.image}`;
imageInfo.innerHTML = `✅ HD Image generated in ${data.generation_time.toFixed(2)} seconds | <strong>${data.width}x${data.height} (HD)</strong> | Steps: ${data.steps} | Guidance: ${data.guidance_scale}<br>📸 Model: ${data.model_used || 'SDXL-Turbo'} | Quality: High Definition`;
imageContainer.style.display = 'block';
currentImageData = data.image;
statusDiv.innerHTML = '<div class="status success">✨ HD Image generated successfully! Check the stunning quality below.</div>';
} else {
statusDiv.innerHTML = `<div class="status error">❌ Error: ${data.detail}</div>`;
}
} catch (error) {
statusDiv.innerHTML = `<div class="status error">❌ Error: ${error.message}</div>`;
} finally {
generateBtn.disabled = false;
generateBtn.innerHTML = '🚀 Generate HD Image';
}
});
downloadBtn.addEventListener('click', () => {
if (currentImageData) {
const link = document.createElement('a');
link.download = `hd_image_${Date.now()}_512x512.png`;
link.href = `data:image/png;base64,${currentImageData}`;
link.click();
}
});
</script>
</body>
</html>
"""
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
logger.info("Starting HD Image Generator...")
import threading
thread = threading.Thread(target=load_model)
thread.start()
yield
# Shutdown
logger.info("Shutting down...")
# Update app to use lifespan
app = FastAPI(title="HD CPU Text-to-Image Generator", lifespan=lifespan)
@app.get("/", response_class=HTMLResponse)
async def get_root():
return HTMLResponse(content=HTML_TEMPLATE)
@app.get("/model_status")
async def model_status():
"""Check model loading status"""
return {
"model_loaded": model_loaded,
"model_name": model_name if model_loaded else None,
"fixed_resolution": f"{FIXED_WIDTH}x{FIXED_HEIGHT} (HD)"
}
@app.post("/generate")
async def generate_image(
prompt: str = Form(...),
negative_prompt: str = Form(""),
steps: int = Form(4),
guidance_scale: float = Form(2.0)
):
"""
Generate HD image (FIXED 512x512 resolution)
Optimized for best quality with turbo models
"""
global pipe, model_loaded
if not prompt or len(prompt.strip()) == 0:
raise HTTPException(status_code=400, detail="Prompt cannot be empty")
# Wait for model to load
if not model_loaded or pipe is None:
return JSONResponse(
status_code=202,
content={"detail": "Model is still loading (first time). Please wait 2-3 minutes and try again."}
)
try:
start_time = time.time()
# OPTIMAL HD SETTINGS (Fixed for best quality)
steps = max(1, min(6, steps)) # Limit to 1-6 for turbo models
guidance_scale = max(1.0, min(3.0, guidance_scale)) # Turbo models work best at 1.5-2.5
# Enhanced negative prompt if not provided
if not negative_prompt or len(negative_prompt.strip()) < 10:
negative_prompt = "blurry, low quality, worst quality, deformed, ugly, bad anatomy, disfigured, missing fingers, extra digits, cropped, jpeg artifacts, lowres, oversmooth, watermark, text, error, messy, draft, imperfect, low resolution, bad composition, overexposed, underexposed, noise, grain"
# Enhance prompt for better quality if it's too short
if len(prompt.split()) < 15:
quality_suffix = ", highly detailed, sharp focus, 8K resolution, professional quality, cinematic lighting, vibrant colors, crisp lines"
prompt = prompt + quality_suffix
logger.info(f"🎨 Generating HD image: '{prompt[:60]}...'")
logger.info(f"⚙️ Settings: {steps} steps, guidance={guidance_scale}, resolution={FIXED_WIDTH}x{FIXED_HEIGHT}")
# Generate image with fixed HD resolution
result = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
num_inference_steps=steps,
guidance_scale=guidance_scale,
width=FIXED_WIDTH, # FIXED HD
height=FIXED_HEIGHT # FIXED HD
)
image = result.images[0]
# Post-process: enhance sharpness for HD quality
from PIL import ImageEnhance
# Slight sharpness enhancement for HD look
enhancer = ImageEnhance.Sharpness(image)
image = enhancer.enhance(1.1) # Subtle sharpness boost
# Color enhancement for vibrant HD output
enhancer = ImageEnhance.Color(image)
image = enhancer.enhance(1.05) # Slight color boost
generation_time = time.time() - start_time
# Convert to base64
buffered = io.BytesIO()
image.save(buffered, format="PNG", quality=95, optimize=True)
img_str = base64.b64encode(buffered.getvalue()).decode()
# Save HD image to file
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"generated_images/hd_image_{timestamp}_512x512.png"
image.save(filename, "PNG", quality=95)
logger.info(f"✅ HD image generated in {generation_time:.2f}s - {FIXED_WIDTH}x{FIXED_HEIGHT}")
return JSONResponse(content={
"image": img_str,
"generation_time": generation_time,
"width": FIXED_WIDTH,
"height": FIXED_HEIGHT,
"steps": steps,
"guidance_scale": guidance_scale,
"filename": filename,
"model_used": model_name,
"quality": "HD (512x512)"
})
except Exception as e:
logger.error(f"Generation error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Generation failed: {str(e)}")
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {
"status": "healthy",
"model_loaded": model_loaded,
"model_name": model_name,
"resolution": f"{FIXED_WIDTH}x{FIXED_HEIGHT} (HD Fixed)",
"recommended_steps": "4-6",
"recommended_guidance": "1.5-2.5"
}
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("PORT", 7860))
uvicorn.run(app, host="0.0.0.0", port=port)