mango_clfd / app.py
alihamzajutt's picture
test 2
065ce4f
Raw
History Blame Contribute Delete
22 kB
import os
from dotenv import load_dotenv
load_dotenv() # Only needed locally
# Prevent libgomp crashes in Spaces
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "0"
os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
os.environ["HTTP_PROXY"] = ""
os.environ["HTTPS_PROXY"] = ""
os.environ["http_proxy"] = ""
os.environ["https_proxy"] = ""
import threading
import requests
from huggingface_hub import snapshot_download
import gradio as gr
import base64
import io
import json
import uuid
# Import pose transfer related modules
try:
from diffusers import DDPMScheduler
from defaults import pose_transfer_C as cfg
from pose_transfer_train import build_model
from models import UNet, VariationalAutoencoder
import torch
import numpy as np
import pandas as pd
from pose_utils import (cords_to_map, draw_pose_from_cords,
load_pose_cords_from_strings)
import random
from PIL import Image
from torchvision import transforms
import copy
except ImportError as e:
print(f"Import error (models not downloaded yet): {e}")
# ==============================
# CONFIGURATION - MODIFIED: Use temp directory instead of persistent
# ==============================
MODEL_REPO = "recky101/new_l_cfld_model"
# Changed from persistent to temporary directory
MODEL_DIR = "/tmp/models" # Temporary directory that gets cleared on restart
# ==============================
# GLOBALS
# ==============================
download_thread = None
download_log = []
cancel_download = False
model_ready = False
vae = None
model = None
unet = None
noise_scheduler = None
test_pairs = None
annotation_file = None
# ==============================
# UTILS
# ==============================
def log(msg: str):
"""Append a log message and return the full log as a string."""
global download_log
download_log.append(msg)
print(msg)
return "\n".join(download_log)
def test_huggingface_connectivity():
"""Test if we can reach Hugging Face servers."""
try:
response = requests.get("https://huggingface.co/", timeout=10)
if response.status_code == 200:
return True, "✅ Can reach Hugging Face"
else:
return False, f"❌ Hugging Face returned status {response.status_code}"
except Exception as e:
return False, f"❌ Cannot reach Hugging Face: {str(e)}"
def is_model_ready():
"""Check if required folders already exist in temp directory."""
# Always return False to force download on each start
return False
def get_directory_tree(root_dir, indent=""):
"""Recursively build a directory tree string for logs."""
tree_str = ""
try:
items = sorted(os.listdir(root_dir))
except Exception as e:
return f"{indent}❌ [Error accessing {root_dir}]: {e}\n"
for i, item in enumerate(items):
path = os.path.join(root_dir, item)
connector = "└── " if i == len(items) - 1 else "├── "
tree_str += f"{indent}{connector}{item}\n"
if os.path.isdir(path):
tree_str += get_directory_tree(path, indent + (" " if i == len(items) - 1 else "│ "))
return tree_str
def download_individual_folders_with_retry():
"""Alternative method with robust retry logic."""
try:
folders_to_download = ["checkpoints", "pretrained_models", "fashion"]
max_retries = 3
for folder in folders_to_download:
if cancel_download:
log("⛔ Download cancelled during individual folder download.")
return
folder_path = os.path.join(MODEL_DIR, folder)
if os.path.exists(folder_path):
log(f"✅ Folder {folder} already exists, skipping...")
continue
for attempt in range(max_retries):
try:
log(f"⬇️ Downloading folder: {folder} (Attempt {attempt + 1}/{max_retries})")
snapshot_download(
repo_id=MODEL_REPO,
local_dir=folder_path,
resume_download=True,
local_dir_use_symlinks=False,
allow_patterns=f"{folder}/*"
)
log(f"✅ Successfully downloaded {folder}")
break
except Exception as e:
if attempt == max_retries - 1:
log(f"❌ Failed to download {folder} after {max_retries} attempts: {str(e)}")
else:
log(f"⚠️ Attempt {attempt + 1} failed for {folder}: {str(e)}")
import time
time.sleep(5)
log("✅ All folders processed.")
except Exception as e:
log(f"❌ Individual folder download failed: {str(e)}")
def download_models():
"""Download models with logging - will download on every start."""
global cancel_download, model_ready, vae, model, unet, noise_scheduler, test_pairs, annotation_file
try:
# Clear previous downloads if they exist
import shutil
if os.path.exists(MODEL_DIR):
shutil.rmtree(MODEL_DIR)
log(f"🧹 Cleared previous model directory: {MODEL_DIR}")
# Test connectivity first
success, message = test_huggingface_connectivity()
log(message)
if not success:
log("🌐 Please check your internet connection and try again")
return
os.makedirs(MODEL_DIR, exist_ok=True)
log("⬇️ Starting model download from Hugging Face Hub...")
log("ℹ️ Models will be downloaded to temporary storage and will be cleared on restart.")
# Add retry logic
max_retries = 3
for attempt in range(max_retries):
try:
if cancel_download:
log("⛔ Download cancelled during process.")
return
log(f"🔄 Attempt {attempt + 1}/{max_retries}")
snapshot_download(
repo_id=MODEL_REPO,
local_dir=MODEL_DIR,
resume_download=True,
local_dir_use_symlinks=False,
)
break
except Exception as e:
if attempt == max_retries - 1:
raise e
log(f"⚠️ Attempt {attempt + 1} failed: {str(e)}")
import time
time.sleep(10)
if cancel_download:
log("⛔ Download cancelled during process.")
return
log("✅ Download completed successfully.")
log("📂 Listing downloaded directory structure...")
tree = get_directory_tree(MODEL_DIR)
log(f"\n{tree}")
model_ready = True
initialize_models()
except Exception as e:
error_msg = str(e)
log(f"❌ Download failed after {max_retries} attempts: {error_msg}")
log("💡 Trying alternative download method...")
download_individual_folders_with_retry()
def initialize_models():
"""Initialize the pose transfer models after download."""
global vae, model, unet, noise_scheduler, test_pairs, annotation_file
try:
log("🔄 Initializing pose transfer models...")
# DEBUG: Print entire directory tree with sizes
log("📂 Verifying model files in temporary directory...")
for root, dirs, files in os.walk(MODEL_DIR):
level = root.replace(MODEL_DIR, "").count(os.sep)
indent = " " * 4 * (level)
log(f"{indent}{os.path.basename(root)}/")
subindent = " " * 4 * (level + 1)
for f in files:
size = os.path.getsize(os.path.join(root, f)) / (1024*1024)
log(f"{subindent}{f} ({size:.2f} MB)")
log("🔄 Initializing pose transfer models...")
# Initialize models
noise_scheduler = DDPMScheduler.from_pretrained(os.path.join(MODEL_DIR, "pretrained_models/scheduler/scheduler_config.json"))
log("✅ Noise scheduler initialized")
vae = VariationalAutoencoder(pretrained_path=os.path.join(MODEL_DIR, "pretrained_models/vae")).eval().requires_grad_(False).cuda()
log("✅ VAE initialized")
model = build_model(cfg).eval().requires_grad_(False).cuda()
log("✅ Main model initialized")
unet = UNet(cfg).eval().requires_grad_(False).cuda()
log("✅ UNet initialized")
# Load model weights
model.load_state_dict(torch.load(
os.path.join(MODEL_DIR, "checkpoints", "pytorch_model.bin"), map_location="cpu"
), strict=False)
log("✅ Model weights loaded")
unet.load_state_dict(torch.load(
os.path.join(MODEL_DIR, "checkpoints", "pytorch_model_1-001.bin"), map_location="cpu"
), strict=False)
log("✅ UNet weights loaded")
# Load test data
test_pairs_path = os.path.join(MODEL_DIR, "fashion", "fasion-resize-pairs-test.csv")
test_pairs = pd.read_csv(test_pairs_path)
log("✅ Test pairs loaded")
annotation_file_path = os.path.join(MODEL_DIR, "fashion", "fasion-resize-annotation-test.csv")
annotation_file = pd.read_csv(annotation_file_path, sep=':')
annotation_file = annotation_file.set_index('name')
log("✅ Annotation file loaded")
log("✅ Models initialized successfully")
except Exception as e:
log(f"❌ Error initializing models: {str(e)}")
def build_pose_img(annotation_file, img_path):
"""Build pose image from annotation file."""
log(f"📄 img_path: {img_path}")
log(f"📄 basename(img_path): {os.path.basename(img_path)}")
log(f"📄 Index Sample: {annotation_file.index[:5]}")
log(f"📄 Does key exist?: {os.path.basename(img_path) in annotation_file.index}")
string = annotation_file.loc[os.path.basename(img_path)]
array = load_pose_cords_from_strings(string['keypoints_y'], string['keypoints_x'])
pose_map = torch.tensor(cords_to_map(array, (256, 256), (256, 176)).transpose(2, 0, 1), dtype=torch.float32)
pose_img = torch.tensor(draw_pose_from_cords(array, (256, 256), (256, 176)).transpose(2, 0, 1) / 255., dtype=torch.float32)
pose_img = torch.cat([pose_img, pose_map], dim=0)
return pose_img
def pose_transfer(source_image, test_pair_index):
test_pair_index = int(test_pair_index)
"""Perform pose transfer from source image to target pose."""
global vae, model, unet, noise_scheduler, test_pairs, annotation_file
if not model_ready:
raise ValueError("Models not ready. Please download models first.")
if test_pair_index < 0 or test_pair_index >= len(test_pairs):
raise ValueError(f"Test pair index must be between 0 and {len(test_pairs)-1}")
# Get target image path
img_to_path = test_pairs.iloc[test_pair_index]["to"]
log(f"🔄 img_to_path: {img_to_path}")
# Build pose image
pose_img_tensor = build_pose_img(annotation_file, img_to_path).unsqueeze(0)
# Transform source image
trans = transforms.Compose([
transforms.Resize([256, 256], interpolation=transforms.InterpolationMode.BICUBIC, antialias=True),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
img_from_tensor = trans(source_image).unsqueeze(0)
# Perform pose transfer
with torch.no_grad():
c_new, down_block_additional_residuals, up_block_additional_residuals = model({
"img_cond": img_from_tensor.cuda(), "pose_img": pose_img_tensor.cuda()})
noisy_latents = torch.randn((1, 4, 64, 64)).cuda()
weight_dtype = torch.float32
bsz = 1
c_new = torch.cat([c_new[:bsz], c_new[:bsz], c_new[bsz:]])
down_block_additional_residuals = [torch.cat([torch.zeros_like(sample), sample, sample]).to(dtype=weight_dtype)
for sample in down_block_additional_residuals]
up_block_additional_residuals = {k: torch.cat([torch.zeros_like(v), torch.zeros_like(v), v]).to(dtype=weight_dtype)
for k, v in up_block_additional_residuals.items()}
noise_scheduler.set_timesteps(cfg.TEST.NUM_INFERENCE_STEPS)
for t in noise_scheduler.timesteps:
inputs = torch.cat([noisy_latents, noisy_latents, noisy_latents], dim=0)
inputs = noise_scheduler.scale_model_input(inputs, timestep=t)
noise_pred = unet(sample=inputs, timestep=t, encoder_hidden_states=c_new,
down_block_additional_residuals=copy.deepcopy(down_block_additional_residuals),
up_block_additional_residuals=copy.deepcopy(up_block_additional_residuals))
noise_pred_uc, noise_pred_down, noise_pred_full = noise_pred.chunk(3)
noise_pred = noise_pred_uc + \
cfg.TEST.DOWN_BLOCK_GUIDANCE_SCALE * (noise_pred_down - noise_pred_uc) + \
cfg.TEST.FULL_GUIDANCE_SCALE * (noise_pred_full - noise_pred_down)
noisy_latents = noise_scheduler.step(noise_pred, t, noisy_latents)[0]
sampling_imgs = vae.decode(noisy_latents) * 0.5 + 0.5 # denormalize
sampling_imgs = sampling_imgs.clamp(0, 1)
# Convert to PIL image
output_img = Image.fromarray(
(sampling_imgs[0] * 255.)
.permute((1, 2, 0))
.long()
.cpu()
.numpy()
.astype(np.uint8)
)
log("✅ Pose transfer completed successfully")
log(f"🔄 output_img size: {output_img.size}")
return output_img
def start_download():
"""Start model download in a separate thread - will always download fresh."""
global download_thread, download_log, cancel_download
if download_thread and download_thread.is_alive():
return "⚠️ Download already running..."
download_log = []
cancel_download = False
download_thread = threading.Thread(target=download_models)
download_thread.start()
return "📥 Download started (fresh download each time)..."
def get_download_status():
"""Get the latest log status."""
global download_thread
status = "\n".join(download_log) if download_log else "Preparing download..."
if download_thread and download_thread.is_alive():
return status + "\n\n⏳ Download in progress..."
elif model_ready:
return status + "\n\n✅ Models are ready and loaded in memory (temporary storage)."
return status
def cancel_download_fn():
"""Cancel request handler."""
global cancel_download
cancel_download = True
log("⛔ Download cancelled by user.")
return "Download cancelled."
# ==============================
# AUTO-START DOWNLOAD ON STARTUP
# ==============================
# Start download automatically when the app starts
print("🚀 Starting model download on startup...")
download_thread = threading.Thread(target=download_models)
download_thread.start()
# ==============================
# GRADIO UI ONLY (NO API ENDPOINTS)
# ==============================
def gradio_pose_transfer(source_image, test_pair_index):
test_pair_index = int(test_pair_index)
"""Gradio interface for pose transfer."""
try:
if not model_ready:
return None, "Models not ready. Please wait for download to complete."
if test_pair_index < 0 or test_pair_index >= len(test_pairs):
return None, f"Test pair index must be between 0 and {len(test_pairs)-1}"
# Perform pose transfer
output_image = pose_transfer(source_image, test_pair_index)
return output_image, "Pose transfer successful"
except Exception as e:
import traceback
tb = traceback.format_exc()
log(f"❌ Error during pose transfer:\n{tb}")
log(f"❌ the value received is as follow: \n{test_pair_index}")
return None, f"Error during pose transfer: {str(e)}"
with gr.Blocks() as demo:
gr.Markdown("## 🧩 Model Downloader & Pose Transfer")
gr.Markdown(f"**Model Source:** [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO})")
gr.Markdown("**Storage:** Models are downloaded to temporary storage and will be cleared on restart.")
gr.Markdown("**Status:** Download starts automatically on app launch.")
with gr.Tab("Model Download"):
with gr.Row():
start_btn = gr.Button("🔄 Re-download Models")
cancel_btn = gr.Button("❌ Cancel Download")
status_box = gr.Textbox(
label="Download Logs",
lines=25,
interactive=False,
placeholder="Models downloading on startup..."
)
# Button bindings
start_btn.click(fn=start_download, inputs=None, outputs=status_box)
cancel_btn.click(fn=cancel_download_fn, inputs=None, outputs=status_box)
# Periodic refresh of logs
demo.load(fn=get_download_status, inputs=None, outputs=status_box, every=2)
with gr.Tab("Pose Transfer"):
gr.Markdown("## Pose Transfer")
with gr.Row():
with gr.Column():
source_image = gr.Image(label="Source Image", type="pil")
test_pair_index = gr.Number(
label="Test Pair Index",
value=0,
minimum=0,
maximum=4039
)
generate_btn = gr.Button("🚀 Generate Pose Transfer")
with gr.Column():
output_image = gr.Image(label="Output Image", type="pil")
status_message = gr.Textbox(label="Status", interactive=False)
generate_btn.click(
fn=gradio_pose_transfer,
inputs=[source_image, test_pair_index],
outputs=[output_image, status_message]
)
from fastapi import FastAPI, Form, UploadFile
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from PIL import Image
import base64
from io import BytesIO
import uvicorn
import cloudinary
import cloudinary.uploader
# Configure Cloudinary from environment variables
cloudinary.config(
cloud_name=os.environ.get("CLOUDINARY_CLOUD_NAME"),
api_key=os.environ.get("CLOUDINARY_API_KEY"),
api_secret=os.environ.get("CLOUDINARY_API_SECRET")
)
app = FastAPI()
# Allow CORS (needed for Postman / frontends)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.post("/pose-transfer")
async def pose_transfer_api(
test_pair_index: int = Form(...),
source_image: UploadFile = None,
image_base64: str = Form(None)
):
try:
# Get input image
if source_image:
image_bytes = await source_image.read()
image = Image.open(BytesIO(image_bytes)).convert("RGB")
elif image_base64:
if "," in image_base64:
image_base64 = image_base64.split(",", 1)[1]
image_bytes = base64.b64decode(image_base64)
image = Image.open(BytesIO(image_bytes)).convert("RGB")
else:
return JSONResponse(
content={"status": "error", "message": "No image provided"},
status_code=400
)
# Check if models are ready
if not model_ready:
return JSONResponse(
content={"status": "error", "message": "Models are still downloading. Please wait."},
status_code=503
)
# Check if Cloudinary is configured
if not all([cloudinary.config().cloud_name, cloudinary.config().api_key, cloudinary.config().api_secret]):
return JSONResponse(
content={"status": "error", "message": "Cloudinary not properly configured"},
status_code=500
)
# 🔹 Call your pose transfer model
output_image = pose_transfer(image, test_pair_index)
# Save image to buffer
buffer = BytesIO()
output_image.save(buffer, format="PNG")
buffer.seek(0)
# Upload to Cloudinary
upload_result = cloudinary.uploader.upload(
buffer,
folder="pose_transfer", # Optional folder in Cloudinary
public_id=f"pose_transfer_{uuid.uuid4().hex[:8]}", # Unique ID
overwrite=True,
resource_type="image"
)
# Get the URL from Cloudinary response
image_url = upload_result.get('secure_url', upload_result.get('url'))
return JSONResponse(
content={
"status": "success",
"message": "Pose transfer completed and image uploaded to Cloudinary",
"image_url": image_url
}
)
except Exception as e:
import traceback
tb = traceback.format_exc()
return JSONResponse(
content={"status": "error", "message": str(e), "traceback": tb},
status_code=500
)
if __name__ == "__main__":
# Note: Models will be downloaded automatically on startup via the thread started above
# No need to check if models exist since we always download fresh
# Launch both Gradio and FastAPI
# You might want to run them on different ports or use a different approach
# For simplicity, we'll just run the FastAPI app which includes the Gradio interface
uvicorn.run(app, host="0.0.0.0", port=7860)