hamzaanwar12 commited on
Commit
21d64bc
·
1 Parent(s): 6f58b92

check claude result

Browse files
Files changed (1) hide show
  1. app.py +377 -70
app.py CHANGED
@@ -1,7 +1,6 @@
1
  import os
2
  # Prevent libgomp crashes in Spaces
3
  os.environ["OMP_NUM_THREADS"] = "1"
4
-
5
  os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "0"
6
  os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
7
  os.environ["HTTP_PROXY"] = ""
@@ -17,6 +16,15 @@ import base64
17
  import io
18
  import json
19
  import uuid
 
 
 
 
 
 
 
 
 
20
 
21
  # Import pose transfer related modules
22
  try:
@@ -42,6 +50,9 @@ except ImportError as e:
42
  MODEL_REPO = "recky101/new_l_cfld_model"
43
  PERSISTENT_DIR = "/data/models"
44
 
 
 
 
45
  # ==============================
46
  # GLOBALS
47
  # ==============================
@@ -57,7 +68,41 @@ test_pairs = None
57
  annotation_file = None
58
 
59
  # ==============================
60
- # UTILS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  # ==============================
62
  def log(msg: str):
63
  """Append a log message and return the full log as a string."""
@@ -210,7 +255,6 @@ def download_models():
210
  log("💡 Trying alternative download method...")
211
  download_individual_folders_with_retry()
212
 
213
-
214
  def initialize_models():
215
  """Initialize the pose transfer models after download."""
216
  global vae, model, unet, noise_scheduler, test_pairs, annotation_file
@@ -229,9 +273,9 @@ def initialize_models():
229
  size = os.path.getsize(os.path.join(root, f)) / (1024*1024)
230
  log(f"{subindent}{f} ({size:.2f} MB)")
231
 
232
- log("🔄 Initializing pose transfer models after tyeh checking ll thigns out...")
233
  try:
234
- # Initialize models
235
  noise_scheduler = DDPMScheduler.from_pretrained(os.path.join(PERSISTENT_DIR, "pretrained_models/scheduler/scheduler_config.json"))
236
  log("🔄 noise done")
237
  vae = VariationalAutoencoder(pretrained_path=os.path.join(PERSISTENT_DIR, "pretrained_models/vae")).eval().requires_grad_(False).cuda()
@@ -244,15 +288,16 @@ def initialize_models():
244
  except Exception as e:
245
  log(f"❌ Error during model initialization: {str(e)}")
246
  log(e)
 
247
  # Load model weights
248
  model.load_state_dict(torch.load(
249
  os.path.join(PERSISTENT_DIR, "checkpoints", "pytorch_model.bin"), map_location="cpu"
250
  ), strict=False)
251
- log("🔄 checkoitns done")
252
  unet.load_state_dict(torch.load(
253
  os.path.join(PERSISTENT_DIR, "checkpoints", "pytorch_model_1-001.bin"), map_location="cpu"
254
  ), strict=False)
255
- log("🔄 checkoitns22 done ")
256
 
257
  # Load test data
258
  test_pairs_path = os.path.join(PERSISTENT_DIR, "fashion", "fasion-resize-pairs-test.csv")
@@ -262,8 +307,6 @@ def initialize_models():
262
  annotation_file = pd.read_csv(annotation_file_path, sep=':')
263
  annotation_file = annotation_file.set_index('name')
264
 
265
-
266
-
267
  log("✅ Models initialized successfully")
268
 
269
  except Exception as e:
@@ -276,7 +319,6 @@ def build_pose_img(annotation_file, img_path):
276
  log(f"📄 Index Sample: {annotation_file.index[:5]}")
277
  log(f"📄 Does key exist?: {os.path.basename(img_path) in annotation_file.index}")
278
 
279
-
280
  string = annotation_file.loc[os.path.basename(img_path)]
281
  array = load_pose_cords_from_strings(string['keypoints_y'], string['keypoints_x'])
282
  pose_map = torch.tensor(cords_to_map(array, (256, 256), (256, 176)).transpose(2, 0, 1), dtype=torch.float32)
@@ -285,7 +327,6 @@ def build_pose_img(annotation_file, img_path):
285
  return pose_img
286
 
287
  def pose_transfer(source_image, test_pair_index):
288
- test_pair_index = int(test_pair_index)
289
  """Perform pose transfer from source image to target pose."""
290
  global vae, model, unet, noise_scheduler, test_pairs, annotation_file
291
 
@@ -298,6 +339,7 @@ def pose_transfer(source_image, test_pair_index):
298
  # Get target image path
299
  img_to_path = test_pairs.iloc[test_pair_index]["to"]
300
  log(f"🔄 img_to_path: {img_to_path}")
 
301
  # Build pose image
302
  pose_img_tensor = build_pose_img(annotation_file, img_to_path).unsqueeze(0)
303
 
@@ -340,9 +382,7 @@ def pose_transfer(source_image, test_pair_index):
340
  sampling_imgs = vae.decode(noisy_latents) * 0.5 + 0.5 # denormalize
341
  sampling_imgs = sampling_imgs.clamp(0, 1)
342
 
343
- # Convert to PIL image
344
- # output_img = Image.fromarray((sampling_imgs[0] * 255.).permute((1, 2, 0)).long().cpu().numpy().astype(np.uint8)).resize((256, 256))
345
- # Convert tensor to PIL image without resizing
346
  output_img = Image.fromarray(
347
  (sampling_imgs[0] * 255.)
348
  .permute((1, 2, 0))
@@ -352,20 +392,227 @@ def pose_transfer(source_image, test_pair_index):
352
  .astype(np.uint8)
353
  )
354
 
355
- # ✅ Save image in-memory as PNG, preserving original size
356
- img_bytes = io.BytesIO()
357
- output_img.save(img_bytes, format="PNG")
358
- img_bytes.seek(0)
359
-
360
  log("✅ Pose transfer completed successfully")
361
  log(f"🔄 output_img size: {output_img.size}")
362
- log(f"🔄 output_img butes: {img_bytes}")
 
363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
 
 
 
 
 
 
 
366
 
367
- return output_img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  def start_download():
370
  """Start model download in a separate thread."""
371
  global download_thread, download_log, cancel_download
@@ -385,8 +632,6 @@ def get_download_status():
385
  if download_thread and download_thread.is_alive():
386
  return status + "\n\n⏳ Download in progress..."
387
  elif is_model_ready():
388
- # tree = get_directory_tree(PERSISTENT_DIR)
389
- # log(f"\n{tree}")
390
  return status + "\n\n✅ Models are ready."
391
  return status
392
 
@@ -397,11 +642,7 @@ def cancel_download_fn():
397
  log("⛔ Download cancelled by user.")
398
  return "Download cancelled."
399
 
400
- # ==============================
401
- # GRADIO UI ONLY (NO API ENDPOINTS)
402
- # ==============================
403
  def gradio_pose_transfer(source_image, test_pair_index):
404
- test_pair_index = int(test_pair_index)
405
  """Gradio interface for pose transfer."""
406
  try:
407
  if not model_ready:
@@ -411,24 +652,17 @@ def gradio_pose_transfer(source_image, test_pair_index):
411
  return None, f"Test pair index must be between 0 and {len(test_pairs)-1}"
412
 
413
  # Perform pose transfer
414
- output_image = pose_transfer(source_image, test_pair_index)
415
 
416
- # Save image to in-memory bytes (PNG)
417
- img_bytes = io.BytesIO()
418
- output_image.save(img_bytes, format="PNG")
419
- img_bytes.seek(0)
420
-
421
-
422
- return output_image, img_bytes, "Pose transfer successful"
423
-
424
 
425
  except Exception as e:
426
  import traceback
427
  tb = traceback.format_exc()
428
  log(f"❌ Error during pose transfer:\n{tb}")
429
- log(f"❌ the value recieved is as follow: \n{test_pair_index}")
430
  return None, f"Error during pose transfer: {str(e)}"
431
 
 
432
  with gr.Blocks() as demo:
433
  gr.Markdown("## 🧩 Model Downloader & Pose Transfer")
434
  gr.Markdown(f"**Model Source:** [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO})")
@@ -469,7 +703,6 @@ with gr.Blocks() as demo:
469
 
470
  with gr.Column():
471
  output_image = gr.Image(label="Output Image", type="pil")
472
- download_btn = gr.File(label="Download Output", file_types=[".png"])
473
  status_message = gr.Textbox(label="Status", interactive=False)
474
 
475
  generate_btn.click(
@@ -478,42 +711,116 @@ with gr.Blocks() as demo:
478
  outputs=[output_image, status_message]
479
  )
480
 
481
- # ==============================
482
- # START APP
483
- # ==============================
484
-
485
-
486
- from fastapi import FastAPI, Request
487
- from fastapi.responses import JSONResponse
488
- import traceback
489
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
 
491
- def read_image_from_input(image_input: str):
492
- """
493
- Accepts either:
494
- - Base64 image string ("data:image/png;base64,...")
495
- - Image URL ("https://...")
496
- Returns: PIL.Image
497
- """
498
- try:
499
- if image_input.startswith("http://") or image_input.startswith("https://"):
500
- response = requests.get(image_input)
501
- response.raise_for_status()
502
- return Image.open(io.BytesIO(response.content)).convert("RGB")
503
- else:
504
- # Base64 input
505
- if "," in image_input:
506
- image_input = image_input.split(",", 1)[1]
507
- image_bytes = base64.b64decode(image_input)
508
- return Image.open(io.BytesIO(image_bytes)).convert("RGB")
509
- except Exception as e:
510
- raise ValueError(f"Invalid image input: {str(e)}")
511
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
 
 
 
513
  if __name__ == "__main__":
514
- # Initialize models if they exist
515
- if is_model_ready():
 
 
 
516
  model_ready = True
517
  initialize_models()
518
 
519
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  # Prevent libgomp crashes in Spaces
3
  os.environ["OMP_NUM_THREADS"] = "1"
 
4
  os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "0"
5
  os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
6
  os.environ["HTTP_PROXY"] = ""
 
16
  import io
17
  import json
18
  import uuid
19
+ from fastapi import FastAPI, HTTPException, UploadFile, File
20
+ from fastapi.responses import JSONResponse, FileResponse
21
+ from pydantic import BaseModel, validator
22
+ from typing import Optional, Union
23
+ import tempfile
24
+ import aiofiles
25
+ import asyncio
26
+ from urllib.parse import urlparse
27
+ import httpx
28
 
29
  # Import pose transfer related modules
30
  try:
 
50
  MODEL_REPO = "recky101/new_l_cfld_model"
51
  PERSISTENT_DIR = "/data/models"
52
 
53
+ # Initialize FastAPI app
54
+ app = FastAPI(title="Pose Transfer API", version="1.0.0", description="API for AI-powered pose transfer")
55
+
56
  # ==============================
57
  # GLOBALS
58
  # ==============================
 
68
  annotation_file = None
69
 
70
  # ==============================
71
+ # PYDANTIC MODELS
72
+ # ==============================
73
+ class PoseTransferRequest(BaseModel):
74
+ source_image: Optional[str] = None # Base64 encoded image
75
+ source_image_url: Optional[str] = None # URL to image
76
+ test_pair_index: int
77
+ output_format: Optional[str] = "base64" # "base64" or "url"
78
+
79
+ @validator('test_pair_index')
80
+ def validate_test_pair_index(cls, v):
81
+ if v < 0 or v >= 4040: # Based on your maximum value
82
+ raise ValueError(f'Test pair index must be between 0 and 4039')
83
+ return v
84
+
85
+ @validator('source_image', 'source_image_url')
86
+ def validate_image_input(cls, v, values, field):
87
+ # Ensure at least one image input is provided
88
+ if field.name == 'source_image_url' and not v and not values.get('source_image'):
89
+ raise ValueError('Either source_image or source_image_url must be provided')
90
+ return v
91
+
92
+ class PoseTransferResponse(BaseModel):
93
+ success: bool
94
+ message: str
95
+ output_image: Optional[str] = None # Base64 encoded or URL
96
+ output_format: str
97
+ processing_time: Optional[float] = None
98
+
99
+ class ModelStatusResponse(BaseModel):
100
+ model_ready: bool
101
+ message: str
102
+ download_progress: Optional[str] = None
103
+
104
+ # ==============================
105
+ # UTILITY FUNCTIONS (Same as your original code)
106
  # ==============================
107
  def log(msg: str):
108
  """Append a log message and return the full log as a string."""
 
255
  log("💡 Trying alternative download method...")
256
  download_individual_folders_with_retry()
257
 
 
258
  def initialize_models():
259
  """Initialize the pose transfer models after download."""
260
  global vae, model, unet, noise_scheduler, test_pairs, annotation_file
 
273
  size = os.path.getsize(os.path.join(root, f)) / (1024*1024)
274
  log(f"{subindent}{f} ({size:.2f} MB)")
275
 
276
+ log("🔄 Initializing pose transfer models after checking all things out...")
277
  try:
278
+ # Initialize models
279
  noise_scheduler = DDPMScheduler.from_pretrained(os.path.join(PERSISTENT_DIR, "pretrained_models/scheduler/scheduler_config.json"))
280
  log("🔄 noise done")
281
  vae = VariationalAutoencoder(pretrained_path=os.path.join(PERSISTENT_DIR, "pretrained_models/vae")).eval().requires_grad_(False).cuda()
 
288
  except Exception as e:
289
  log(f"❌ Error during model initialization: {str(e)}")
290
  log(e)
291
+
292
  # Load model weights
293
  model.load_state_dict(torch.load(
294
  os.path.join(PERSISTENT_DIR, "checkpoints", "pytorch_model.bin"), map_location="cpu"
295
  ), strict=False)
296
+ log("🔄 checkpoints done")
297
  unet.load_state_dict(torch.load(
298
  os.path.join(PERSISTENT_DIR, "checkpoints", "pytorch_model_1-001.bin"), map_location="cpu"
299
  ), strict=False)
300
+ log("🔄 checkpoints22 done ")
301
 
302
  # Load test data
303
  test_pairs_path = os.path.join(PERSISTENT_DIR, "fashion", "fasion-resize-pairs-test.csv")
 
307
  annotation_file = pd.read_csv(annotation_file_path, sep=':')
308
  annotation_file = annotation_file.set_index('name')
309
 
 
 
310
  log("✅ Models initialized successfully")
311
 
312
  except Exception as e:
 
319
  log(f"📄 Index Sample: {annotation_file.index[:5]}")
320
  log(f"📄 Does key exist?: {os.path.basename(img_path) in annotation_file.index}")
321
 
 
322
  string = annotation_file.loc[os.path.basename(img_path)]
323
  array = load_pose_cords_from_strings(string['keypoints_y'], string['keypoints_x'])
324
  pose_map = torch.tensor(cords_to_map(array, (256, 256), (256, 176)).transpose(2, 0, 1), dtype=torch.float32)
 
327
  return pose_img
328
 
329
  def pose_transfer(source_image, test_pair_index):
 
330
  """Perform pose transfer from source image to target pose."""
331
  global vae, model, unet, noise_scheduler, test_pairs, annotation_file
332
 
 
339
  # Get target image path
340
  img_to_path = test_pairs.iloc[test_pair_index]["to"]
341
  log(f"🔄 img_to_path: {img_to_path}")
342
+
343
  # Build pose image
344
  pose_img_tensor = build_pose_img(annotation_file, img_to_path).unsqueeze(0)
345
 
 
382
  sampling_imgs = vae.decode(noisy_latents) * 0.5 + 0.5 # denormalize
383
  sampling_imgs = sampling_imgs.clamp(0, 1)
384
 
385
+ # Convert tensor to PIL image
 
 
386
  output_img = Image.fromarray(
387
  (sampling_imgs[0] * 255.)
388
  .permute((1, 2, 0))
 
392
  .astype(np.uint8)
393
  )
394
 
 
 
 
 
 
395
  log("✅ Pose transfer completed successfully")
396
  log(f"🔄 output_img size: {output_img.size}")
397
+
398
+ return output_img
399
 
400
+ # ==============================
401
+ # IMAGE PROCESSING UTILITIES
402
+ # ==============================
403
+ async def load_image_from_base64(base64_str: str) -> Image.Image:
404
+ """Load PIL Image from base64 string."""
405
+ try:
406
+ # Remove data URL prefix if present
407
+ if base64_str.startswith('data:image'):
408
+ base64_str = base64_str.split(',', 1)[1]
409
+
410
+ # Decode base64
411
+ image_data = base64.b64decode(base64_str)
412
+ image = Image.open(io.BytesIO(image_data))
413
+
414
+ # Convert to RGB if necessary
415
+ if image.mode != 'RGB':
416
+ image = image.convert('RGB')
417
+
418
+ return image
419
+ except Exception as e:
420
+ raise HTTPException(status_code=400, detail=f"Invalid base64 image: {str(e)}")
421
 
422
+ async def load_image_from_url(url: str) -> Image.Image:
423
+ """Load PIL Image from URL."""
424
+ try:
425
+ # Validate URL
426
+ parsed_url = urlparse(url)
427
+ if not parsed_url.scheme or not parsed_url.netloc:
428
+ raise HTTPException(status_code=400, detail="Invalid URL format")
429
+
430
+ # Download image
431
+ async with httpx.AsyncClient(timeout=30.0) as client:
432
+ response = await client.get(url)
433
+ response.raise_for_status()
434
+
435
+ # Check content type
436
+ content_type = response.headers.get('content-type', '')
437
+ if not content_type.startswith('image/'):
438
+ raise HTTPException(status_code=400, detail="URL does not point to an image")
439
+
440
+ # Load image
441
+ image = Image.open(io.BytesIO(response.content))
442
+
443
+ # Convert to RGB if necessary
444
+ if image.mode != 'RGB':
445
+ image = image.convert('RGB')
446
+
447
+ return image
448
+
449
+ except httpx.HTTPError as e:
450
+ raise HTTPException(status_code=400, detail=f"Failed to download image: {str(e)}")
451
+ except Exception as e:
452
+ raise HTTPException(status_code=500, detail=f"Error processing image from URL: {str(e)}")
453
 
454
+ def pil_to_base64(image: Image.Image, format: str = "PNG") -> str:
455
+ """Convert PIL Image to base64 string."""
456
+ buffer = io.BytesIO()
457
+ image.save(buffer, format=format)
458
+ buffer.seek(0)
459
+ img_str = base64.b64encode(buffer.getvalue()).decode()
460
+ return f"data:image/{format.lower()};base64,{img_str}"
461
 
462
+ # ==============================
463
+ # API ENDPOINTS
464
+ # ==============================
465
+ @app.get("/")
466
+ async def root():
467
+ """Root endpoint with API information."""
468
+ return {
469
+ "message": "Pose Transfer API",
470
+ "version": "1.0.0",
471
+ "endpoints": {
472
+ "POST /pose-transfer": "Perform pose transfer",
473
+ "GET /model-status": "Check model status",
474
+ "POST /download-models": "Download models",
475
+ "GET /health": "Health check"
476
+ }
477
+ }
478
+
479
+ @app.get("/health")
480
+ async def health_check():
481
+ """Health check endpoint."""
482
+ return {
483
+ "status": "healthy",
484
+ "model_ready": model_ready,
485
+ "timestamp": pd.Timestamp.now().isoformat()
486
+ }
487
+
488
+ @app.get("/model-status", response_model=ModelStatusResponse)
489
+ async def get_model_status():
490
+ """Get current model status and download progress."""
491
+ global model_ready, download_log, download_thread
492
+
493
+ is_downloading = download_thread and download_thread.is_alive()
494
+ progress = "\n".join(download_log) if download_log else None
495
+
496
+ return ModelStatusResponse(
497
+ model_ready=model_ready,
498
+ message="Models are ready" if model_ready else ("Download in progress" if is_downloading else "Models not downloaded"),
499
+ download_progress=progress
500
+ )
501
+
502
+ @app.post("/download-models")
503
+ async def start_model_download():
504
+ """Start model download process."""
505
+ global download_thread, download_log, cancel_download
506
+
507
+ if download_thread and download_thread.is_alive():
508
+ return JSONResponse(
509
+ status_code=409,
510
+ content={"message": "Download already in progress"}
511
+ )
512
+
513
+ if model_ready:
514
+ return JSONResponse(
515
+ content={"message": "Models already downloaded and ready"}
516
+ )
517
+
518
+ # Start download in background
519
+ download_log = []
520
+ cancel_download = False
521
+ download_thread = threading.Thread(target=download_models)
522
+ download_thread.start()
523
+
524
+ return JSONResponse(
525
+ content={"message": "Model download started"}
526
+ )
527
+
528
+ @app.post("/pose-transfer", response_model=PoseTransferResponse)
529
+ async def api_pose_transfer(request: PoseTransferRequest):
530
+ """Perform pose transfer via API."""
531
+ import time
532
+ start_time = time.time()
533
+
534
+ try:
535
+ # Check if models are ready
536
+ if not model_ready:
537
+ raise HTTPException(
538
+ status_code=503,
539
+ detail="Models not ready. Please download models first using /download-models endpoint."
540
+ )
541
+
542
+ # Load source image
543
+ source_image = None
544
+ if request.source_image:
545
+ source_image = await load_image_from_base64(request.source_image)
546
+ elif request.source_image_url:
547
+ source_image = await load_image_from_url(request.source_image_url)
548
+ else:
549
+ raise HTTPException(
550
+ status_code=400,
551
+ detail="Either source_image (base64) or source_image_url must be provided"
552
+ )
553
+
554
+ # Perform pose transfer
555
+ try:
556
+ output_image = pose_transfer(source_image, request.test_pair_index)
557
+ except ValueError as e:
558
+ raise HTTPException(status_code=400, detail=str(e))
559
+ except Exception as e:
560
+ raise HTTPException(status_code=500, detail=f"Pose transfer failed: {str(e)}")
561
+
562
+ # Format output based on requested format
563
+ processing_time = time.time() - start_time
564
+
565
+ if request.output_format == "base64":
566
+ output_base64 = pil_to_base64(output_image)
567
+ return PoseTransferResponse(
568
+ success=True,
569
+ message="Pose transfer completed successfully",
570
+ output_image=output_base64,
571
+ output_format="base64",
572
+ processing_time=processing_time
573
+ )
574
+ else:
575
+ # For URL format, save to temporary file and return file path
576
+ # In production, you might want to save to cloud storage instead
577
+ temp_filename = f"pose_transfer_{uuid.uuid4()}.png"
578
+ temp_path = os.path.join(tempfile.gettempdir(), temp_filename)
579
+ output_image.save(temp_path, "PNG")
580
+
581
+ return PoseTransferResponse(
582
+ success=True,
583
+ message="Pose transfer completed successfully",
584
+ output_image=f"/download/{temp_filename}",
585
+ output_format="url",
586
+ processing_time=processing_time
587
+ )
588
+
589
+ except HTTPException:
590
+ raise
591
+ except Exception as e:
592
+ processing_time = time.time() - start_time
593
+ return PoseTransferResponse(
594
+ success=False,
595
+ message=f"An error occurred: {str(e)}",
596
+ processing_time=processing_time
597
+ )
598
 
599
+ @app.get("/download/{filename}")
600
+ async def download_file(filename: str):
601
+ """Download generated image files."""
602
+ file_path = os.path.join(tempfile.gettempdir(), filename)
603
+
604
+ if not os.path.exists(file_path):
605
+ raise HTTPException(status_code=404, detail="File not found")
606
+
607
+ return FileResponse(
608
+ path=file_path,
609
+ filename=filename,
610
+ media_type='image/png'
611
+ )
612
+
613
+ # ==============================
614
+ # GRADIO UI (Keep existing functionality)
615
+ # ==============================
616
  def start_download():
617
  """Start model download in a separate thread."""
618
  global download_thread, download_log, cancel_download
 
632
  if download_thread and download_thread.is_alive():
633
  return status + "\n\n⏳ Download in progress..."
634
  elif is_model_ready():
 
 
635
  return status + "\n\n✅ Models are ready."
636
  return status
637
 
 
642
  log("⛔ Download cancelled by user.")
643
  return "Download cancelled."
644
 
 
 
 
645
  def gradio_pose_transfer(source_image, test_pair_index):
 
646
  """Gradio interface for pose transfer."""
647
  try:
648
  if not model_ready:
 
652
  return None, f"Test pair index must be between 0 and {len(test_pairs)-1}"
653
 
654
  # Perform pose transfer
655
+ output_image = pose_transfer(source_image, int(test_pair_index))
656
 
657
+ return output_image, "Pose transfer successful"
 
 
 
 
 
 
 
658
 
659
  except Exception as e:
660
  import traceback
661
  tb = traceback.format_exc()
662
  log(f"❌ Error during pose transfer:\n{tb}")
 
663
  return None, f"Error during pose transfer: {str(e)}"
664
 
665
+ # Create Gradio interface
666
  with gr.Blocks() as demo:
667
  gr.Markdown("## 🧩 Model Downloader & Pose Transfer")
668
  gr.Markdown(f"**Model Source:** [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO})")
 
703
 
704
  with gr.Column():
705
  output_image = gr.Image(label="Output Image", type="pil")
 
706
  status_message = gr.Textbox(label="Status", interactive=False)
707
 
708
  generate_btn.click(
 
711
  outputs=[output_image, status_message]
712
  )
713
 
714
+ with gr.Tab("API Documentation"):
715
+ gr.Markdown("""
716
+ ## API Endpoints
717
+
718
+ ### POST /pose-transfer
719
+ Perform pose transfer with JSON request:
720
+ ```json
721
+ {
722
+ "source_image": "base64_encoded_image_string", // Optional
723
+ "source_image_url": "https://example.com/image.jpg", // Optional
724
+ "test_pair_index": 42,
725
+ "output_format": "base64" // "base64" or "url"
726
+ }
727
+ ```
728
+
729
+ ### GET /model-status
730
+ Check if models are downloaded and ready
731
+
732
+ ### POST /download-models
733
+ Start model download process
734
+
735
+ ### GET /health
736
+ Health check endpoint
737
+
738
+ ## Example Usage (Python)
739
+ ```python
740
+ import requests
741
+ import base64
742
+
743
+ # Load and encode image
744
+ with open("source_image.jpg", "rb") as f:
745
+ image_base64 = base64.b64encode(f.read()).decode()
746
+
747
+ # API request
748
+ response = requests.post("http://localhost:7860/pose-transfer", json={
749
+ "source_image": image_base64,
750
+ "test_pair_index": 100,
751
+ "output_format": "base64"
752
+ })
753
+
754
+ result = response.json()
755
+ if result["success"]:
756
+ # Save output image
757
+ output_base64 = result["output_image"].split(",")[1]
758
+ with open("output_image.png", "wb") as f:
759
+ f.write(base64.b64decode(output_base64))
760
+ print(f"Processing time: {result['processing_time']:.2f}s")
761
+ ```
762
+
763
+ ## Example Usage (cURL)
764
+ ```bash
765
+ # Using base64 image
766
+ curl -X POST "http://localhost:7860/pose-transfer" \
767
+ -H "Content-Type: application/json" \
768
+ -d '{
769
+ "source_image": "iVBORw0KGgoAAAANSUhEUgAA...",
770
+ "test_pair_index": 100,
771
+ "output_format": "base64"
772
+ }'
773
+
774
+ # Using image URL
775
+ curl -X POST "http://localhost:7860/pose-transfer" \
776
+ -H "Content-Type: application/json" \
777
+ -d '{
778
+ "source_image_url": "https://example.com/image.jpg",
779
+ "test_pair_index": 100,
780
+ "output_format": "url"
781
+ }'
782
+ # Mount FastAPI app with Gradio
783
+ """)
784
 
785
+
786
+
787
+ app.mount("/gradio", gr.mount_gradio_app(demo, app, path="/gradio"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788
 
789
+ # ==============================
790
+ # STARTUP EVENT
791
+ # ==============================
792
+ @app.on_event("startup")
793
+ async def startup_event():
794
+ """Initialize models on startup if available."""
795
+ global model_ready
796
+ if is_model_ready() and not model_ready:
797
+ log("🚀 Starting up API server...")
798
+ log("🔍 Models found on startup, initializing...")
799
+ # Run initialization in background thread to avoid blocking startup
800
+ init_thread = threading.Thread(target=initialize_models)
801
+ init_thread.start()
802
 
803
+ # ==============================
804
+ # MAIN EXECUTION
805
+ # ==============================
806
  if __name__ == "__main__":
807
+ import uvicorn
808
+
809
+ # Check if models are ready on startup
810
+ if is_model_ready() and not model_ready:
811
+ print("📁 Models found, initializing...")
812
  model_ready = True
813
  initialize_models()
814
 
815
+ # Run both FastAPI and Gradio
816
+ print("🚀 Starting Pose Transfer API server...")
817
+ print("📱 Gradio UI available at: http://localhost:7860/gradio")
818
+ print("🔌 API endpoints available at: http://localhost:7860")
819
+ print("📖 API documentation at: http://localhost:7860/docs")
820
+
821
+ uvicorn.run(
822
+ app,
823
+ host="0.0.0.0",
824
+ port=7860,
825
+ log_level="info"
826
+ )