hamzaanwar12 commited on
Commit
a1eb84f
·
1 Parent(s): 4faac23

new app.py with pose transfer jugaar

Browse files
Files changed (2) hide show
  1. app.py +275 -19
  2. download.py +239 -0
app.py CHANGED
@@ -10,6 +10,29 @@ import threading
10
  import requests
11
  from huggingface_hub import snapshot_download
12
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # ==============================
15
  # CONFIGURATION
@@ -23,6 +46,13 @@ PERSISTENT_DIR = "/data/models"
23
  download_thread = None
24
  download_log = []
25
  cancel_download = False
 
 
 
 
 
 
 
26
 
27
  # ==============================
28
  # UTILS
@@ -113,7 +143,7 @@ def download_individual_folders_with_retry():
113
 
114
  def download_models():
115
  """Download models with logging and show directory tree after completion."""
116
- global cancel_download
117
 
118
  try:
119
  # Test connectivity first
@@ -128,6 +158,8 @@ def download_models():
128
  log("📂 Directory structure:")
129
  tree = get_directory_tree(PERSISTENT_DIR)
130
  log(f"\n{tree}")
 
 
131
  return
132
 
133
  os.makedirs(PERSISTENT_DIR, exist_ok=True)
@@ -167,6 +199,8 @@ def download_models():
167
  log("📂 Listing downloaded directory structure...")
168
  tree = get_directory_tree(PERSISTENT_DIR)
169
  log(f"\n{tree}")
 
 
170
 
171
  except Exception as e:
172
  error_msg = str(e)
@@ -174,8 +208,108 @@ def download_models():
174
  log("💡 Trying alternative download method...")
175
  download_individual_folders_with_retry()
176
 
177
- # ... rest of your code remains the same ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  def start_download():
181
  """Start model download in a separate thread."""
@@ -206,34 +340,156 @@ def cancel_download_fn():
206
  log("⛔ Download cancelled by user.")
207
  return "Download cancelled."
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  # ==============================
210
  # GRADIO UI
211
  # ==============================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  with gr.Blocks() as demo:
213
- gr.Markdown("## 🧩 Model Downloader with Live Status & Persistent Storage")
214
  gr.Markdown(f"**Model Source:** [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO})")
215
  gr.Markdown("**Required folders:** checkpoints/, pretrained_models/, fashion/")
216
 
217
- with gr.Row():
218
- start_btn = gr.Button("📥 Download Models")
219
- cancel_btn = gr.Button(" Cancel Download")
 
220
 
221
- status_box = gr.Textbox(
222
- label="Download Logs",
223
- lines=25,
224
- interactive=False,
225
- placeholder="Click 'Download Models' to start downloading..."
226
- )
227
 
228
- # Button bindings
229
- start_btn.click(fn=start_download, inputs=None, outputs=status_box)
230
- cancel_btn.click(fn=cancel_download_fn, inputs=None, outputs=status_box)
231
 
232
- # Periodic refresh of logs
233
- demo.load(fn=get_download_status, inputs=None, outputs=status_box, every=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
  # ==============================
236
- # START APP
237
  # ==============================
238
  if __name__ == "__main__":
239
- demo.launch()
 
 
 
 
 
 
 
 
 
 
10
  import requests
11
  from huggingface_hub import snapshot_download
12
  import gradio as gr
13
+ import base64
14
+ import io
15
+ import json
16
+ from fastapi import FastAPI, HTTPException
17
+ from fastapi.middleware.cors import CORSMiddleware
18
+ from fastapi.staticfiles import StaticFiles
19
+ from pydantic import BaseModel
20
+ import uuid
21
+
22
+ # Import pose transfer related modules
23
+ from diffusers import DDPMScheduler
24
+ from defaults import pose_transfer_C as cfg
25
+ from pose_transfer_train import build_model
26
+ from models import UNet, VariationalAutoencoder
27
+ import torch
28
+ import numpy as np
29
+ import pandas as pd
30
+ from pose_utils import (cords_to_map, draw_pose_from_cords,
31
+ load_pose_cords_from_strings)
32
+ import random
33
+ from PIL import Image
34
+ from torchvision import transforms
35
+ import copy
36
 
37
  # ==============================
38
  # CONFIGURATION
 
46
  download_thread = None
47
  download_log = []
48
  cancel_download = False
49
+ model_ready = False
50
+ vae = None
51
+ model = None
52
+ unet = None
53
+ noise_scheduler = None
54
+ test_pairs = None
55
+ annotation_file = None
56
 
57
  # ==============================
58
  # UTILS
 
143
 
144
  def download_models():
145
  """Download models with logging and show directory tree after completion."""
146
+ global cancel_download, model_ready, vae, model, unet, noise_scheduler, test_pairs, annotation_file
147
 
148
  try:
149
  # Test connectivity first
 
158
  log("📂 Directory structure:")
159
  tree = get_directory_tree(PERSISTENT_DIR)
160
  log(f"\n{tree}")
161
+ model_ready = True
162
+ initialize_models()
163
  return
164
 
165
  os.makedirs(PERSISTENT_DIR, exist_ok=True)
 
199
  log("📂 Listing downloaded directory structure...")
200
  tree = get_directory_tree(PERSISTENT_DIR)
201
  log(f"\n{tree}")
202
+ model_ready = True
203
+ initialize_models()
204
 
205
  except Exception as e:
206
  error_msg = str(e)
 
208
  log("💡 Trying alternative download method...")
209
  download_individual_folders_with_retry()
210
 
211
+ def initialize_models():
212
+ """Initialize the pose transfer models after download."""
213
+ global vae, model, unet, noise_scheduler, test_pairs, annotation_file
214
+
215
+ try:
216
+ log("🔄 Initializing pose transfer models...")
217
+
218
+ # Initialize models
219
+ noise_scheduler = DDPMScheduler.from_pretrained(os.path.join(PERSISTENT_DIR, "pretrained_models/scheduler/scheduler_config.json"))
220
+ vae = VariationalAutoencoder(pretrained_path=os.path.join(PERSISTENT_DIR, "pretrained_models/vae")).eval().requires_grad_(False).cuda()
221
+ model = build_model(cfg).eval().requires_grad_(False).cuda()
222
+ unet = UNet(cfg).eval().requires_grad_(False).cuda()
223
+
224
+ # Load model weights
225
+ model.load_state_dict(torch.load(
226
+ os.path.join(PERSISTENT_DIR, "checkpoints", "pytorch_model.bin"), map_location="cpu"
227
+ ), strict=False)
228
+ unet.load_state_dict(torch.load(
229
+ os.path.join(PERSISTENT_DIR, "checkpoints", "pytorch_model_1.bin"), map_location="cpu"
230
+ ), strict=False)
231
+
232
+ # Load test data
233
+ test_pairs_path = os.path.join(PERSISTENT_DIR, "fashion", "fasion-resize-pairs-test.csv")
234
+ test_pairs = pd.read_csv(test_pairs_path)
235
+
236
+ annotation_file_path = os.path.join(PERSISTENT_DIR, "fashion", "fasion-resize-annotation-test.csv")
237
+ annotation_file = pd.read_csv(annotation_file_path, sep=':')
238
+ annotation_file = annotation_file.set_index('name')
239
+
240
+ log("✅ Models initialized successfully")
241
+
242
+ except Exception as e:
243
+ log(f"❌ Error initializing models: {str(e)}")
244
+
245
+ def build_pose_img(annotation_file, img_path):
246
+ """Build pose image from annotation file."""
247
+ string = annotation_file.loc[os.path.basename(img_path)]
248
+ array = load_pose_cords_from_strings(string['keypoints_y'], string['keypoints_x'])
249
+ pose_map = torch.tensor(cords_to_map(array, (256, 256), (256, 176)).transpose(2, 0, 1), dtype=torch.float32)
250
+ pose_img = torch.tensor(draw_pose_from_cords(array, (256, 256), (256, 176)).transpose(2, 0, 1) / 255., dtype=torch.float32)
251
+ pose_img = torch.cat([pose_img, pose_map], dim=0)
252
+ return pose_img
253
+
254
+ def pose_transfer(source_image, test_pair_index):
255
+ """Perform pose transfer from source image to target pose."""
256
+ global vae, model, unet, noise_scheduler, test_pairs, annotation_file
257
+
258
+ if not model_ready:
259
+ raise ValueError("Models not ready. Please download models first.")
260
+
261
+ if test_pair_index < 0 or test_pair_index >= len(test_pairs):
262
+ raise ValueError(f"Test pair index must be between 0 and {len(test_pairs)-1}")
263
+
264
+ # Get target image path
265
+ img_to_path = test_pairs.iloc[test_pair_index]["to"]
266
+
267
+ # Build pose image
268
+ pose_img_tensor = build_pose_img(annotation_file, img_to_path).unsqueeze(0)
269
+
270
+ # Transform source image
271
+ trans = transforms.Compose([
272
+ transforms.Resize([256, 256], interpolation=transforms.InterpolationMode.BICUBIC, antialias=True),
273
+ transforms.ToTensor(),
274
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
275
+ ])
276
+ img_from_tensor = trans(source_image).unsqueeze(0)
277
+
278
+ # Perform pose transfer
279
+ with torch.no_grad():
280
+ c_new, down_block_additional_residuals, up_block_additional_residuals = model({
281
+ "img_cond": img_from_tensor.cuda(), "pose_img": pose_img_tensor.cuda()})
282
+ noisy_latents = torch.randn((1, 4, 64, 64)).cuda()
283
+ weight_dtype = torch.float32
284
+ bsz = 1
285
+
286
+ c_new = torch.cat([c_new[:bsz], c_new[:bsz], c_new[bsz:]])
287
+ down_block_additional_residuals = [torch.cat([torch.zeros_like(sample), sample, sample]).to(dtype=weight_dtype) \
288
+ for sample in down_block_additional_residuals]
289
+ up_block_additional_residuals = {k: torch.cat([torch.zeros_like(v), torch.zeros_like(v), v]).to(dtype=weight_dtype) \
290
+ for k, v in up_block_additional_residuals.items()}
291
+
292
+ noise_scheduler.set_timesteps(cfg.TEST.NUM_INFERENCE_STEPS)
293
+ for t in noise_scheduler.timesteps:
294
+ inputs = torch.cat([noisy_latents, noisy_latents, noisy_latents], dim=0)
295
+ inputs = noise_scheduler.scale_model_input(inputs, timestep=t)
296
+ noise_pred = unet(sample=inputs, timestep=t, encoder_hidden_states=c_new,
297
+ down_block_additional_residuals=copy.deepcopy(down_block_additional_residuals),
298
+ up_block_additional_residuals=copy.deepcopy(up_block_additional_residuals))
299
 
300
+ noise_pred_uc, noise_pred_down, noise_pred_full = noise_pred.chunk(3)
301
+ noise_pred = noise_pred_uc + \
302
+ cfg.TEST.DOWN_BLOCK_GUIDANCE_SCALE * (noise_pred_down - noise_pred_uc) + \
303
+ cfg.TEST.FULL_GUIDANCE_SCALE * (noise_pred_full - noise_pred_down)
304
+ noisy_latents = noise_scheduler.step(noise_pred, t, noisy_latents)[0]
305
+
306
+ sampling_imgs = vae.decode(noisy_latents) * 0.5 + 0.5 # denormalize
307
+ sampling_imgs = sampling_imgs.clamp(0, 1)
308
+
309
+ # Convert to PIL image
310
+ output_img = Image.fromarray((sampling_imgs[0] * 255.).permute((1, 2, 0)).long().cpu().numpy().astype(np.uint8)).resize((256, 256))
311
+
312
+ return output_img
313
 
314
  def start_download():
315
  """Start model download in a separate thread."""
 
340
  log("⛔ Download cancelled by user.")
341
  return "Download cancelled."
342
 
343
+ # ==============================
344
+ # API Models
345
+ # ==============================
346
+ class PoseTransferRequest(BaseModel):
347
+ source_image: str # base64 encoded image
348
+ test_pair_index: int # between 0 and 4039
349
+
350
+ class PoseTransferResponse(BaseModel):
351
+ output_image: str # base64 encoded output image
352
+ success: bool
353
+ message: str
354
+
355
+ # ==============================
356
+ # FastAPI App
357
+ # ==============================
358
+ api_app = FastAPI(title="Pose Transfer API")
359
+
360
+ # Add CORS middleware
361
+ api_app.add_middleware(
362
+ CORSMiddleware,
363
+ allow_origins=["*"],
364
+ allow_credentials=True,
365
+ allow_methods=["*"],
366
+ allow_headers=["*"],
367
+ )
368
+
369
+ @api_app.post("/api/pose-transfer", response_model=PoseTransferResponse)
370
+ async def api_pose_transfer(request: PoseTransferRequest):
371
+ """API endpoint for pose transfer."""
372
+ try:
373
+ if not model_ready:
374
+ return PoseTransferResponse(
375
+ output_image="",
376
+ success=False,
377
+ message="Models not ready. Please download models first."
378
+ )
379
+
380
+ # Decode base64 image
381
+ image_data = base64.b64decode(request.source_image)
382
+ source_image = Image.open(io.BytesIO(image_data)).convert("RGB")
383
+
384
+ # Perform pose transfer
385
+ output_image = pose_transfer(source_image, request.test_pair_index)
386
+
387
+ # Convert output to base64
388
+ buffered = io.BytesIO()
389
+ output_image.save(buffered, format="PNG")
390
+ output_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
391
+
392
+ return PoseTransferResponse(
393
+ output_image=output_base64,
394
+ success=True,
395
+ message="Pose transfer successful"
396
+ )
397
+
398
+ except Exception as e:
399
+ return PoseTransferResponse(
400
+ output_image="",
401
+ success=False,
402
+ message=f"Error during pose transfer: {str(e)}"
403
+ )
404
+
405
+ @api_app.get("/api/status")
406
+ async def api_status():
407
+ """API endpoint to check model status."""
408
+ return {
409
+ "model_ready": model_ready,
410
+ "test_pairs_count": len(test_pairs) if test_pairs is not None else 0
411
+ }
412
+
413
  # ==============================
414
  # GRADIO UI
415
  # ==============================
416
+ def gradio_pose_transfer(source_image, test_pair_index):
417
+ """Gradio interface for pose transfer."""
418
+ try:
419
+ if not model_ready:
420
+ return None, "Models not ready. Please download models first."
421
+
422
+ if test_pair_index < 0 or test_pair_index >= len(test_pairs):
423
+ return None, f"Test pair index must be between 0 and {len(test_pairs)-1}"
424
+
425
+ # Perform pose transfer
426
+ output_image = pose_transfer(source_image, test_pair_index)
427
+
428
+ return output_image, "Pose transfer successful"
429
+
430
+ except Exception as e:
431
+ return None, f"Error during pose transfer: {str(e)}"
432
+
433
  with gr.Blocks() as demo:
434
+ gr.Markdown("## 🧩 Model Downloader & Pose Transfer")
435
  gr.Markdown(f"**Model Source:** [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO})")
436
  gr.Markdown("**Required folders:** checkpoints/, pretrained_models/, fashion/")
437
 
438
+ with gr.Tab("Model Download"):
439
+ with gr.Row():
440
+ start_btn = gr.Button("📥 Download Models")
441
+ cancel_btn = gr.Button("❌ Cancel Download")
442
 
443
+ status_box = gr.Textbox(
444
+ label="Download Logs",
445
+ lines=25,
446
+ interactive=False,
447
+ placeholder="Click 'Download Models' to start downloading..."
448
+ )
449
 
450
+ # Button bindings
451
+ start_btn.click(fn=start_download, inputs=None, outputs=status_box)
452
+ cancel_btn.click(fn=cancel_download_fn, inputs=None, outputs=status_box)
453
 
454
+ # Periodic refresh of logs
455
+ demo.load(fn=get_download_status, inputs=None, outputs=status_box, every=2)
456
+
457
+ with gr.Tab("Pose Transfer"):
458
+ gr.Markdown("## Pose Transfer")
459
+ gr.Markdown(f"Available test pairs: {len(test_pairs) if test_pairs is not None else 'Loading...'}")
460
+
461
+ with gr.Row():
462
+ with gr.Column():
463
+ source_image = gr.Image(label="Source Image", type="pil")
464
+ test_pair_index = gr.Number(
465
+ label="Test Pair Index (0-4039)",
466
+ value=0,
467
+ minimum=0,
468
+ maximum=4039 if test_pairs is not None else 0
469
+ )
470
+ generate_btn = gr.Button("🚀 Generate Pose Transfer")
471
+
472
+ with gr.Column():
473
+ output_image = gr.Image(label="Output Image")
474
+ status_message = gr.Textbox(label="Status", interactive=False)
475
+
476
+ generate_btn.click(
477
+ fn=gradio_pose_transfer,
478
+ inputs=[source_image, test_pair_index],
479
+ outputs=[output_image, status_message]
480
+ )
481
 
482
  # ==============================
483
+ # START APPS
484
  # ==============================
485
  if __name__ == "__main__":
486
+ # Initialize models if they exist
487
+ if is_model_ready():
488
+ model_ready = True
489
+ initialize_models()
490
+
491
+ # Mount the Gradio app
492
+ api_app.mount("/", gr.routes.App.create_app(demo))
493
+
494
+ import uvicorn
495
+ uvicorn.run(api_app, host="0.0.0.0", port=7860)
download.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "0"
3
+ os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
4
+ os.environ["HTTP_PROXY"] = ""
5
+ os.environ["HTTPS_PROXY"] = ""
6
+ os.environ["http_proxy"] = ""
7
+ os.environ["https_proxy"] = ""
8
+
9
+ import threading
10
+ import requests
11
+ from huggingface_hub import snapshot_download
12
+ import gradio as gr
13
+
14
+ # ==============================
15
+ # CONFIGURATION
16
+ # ==============================
17
+ MODEL_REPO = "recky101/new_l_cfld_model"
18
+ PERSISTENT_DIR = "/data/models"
19
+
20
+ # ==============================
21
+ # GLOBALS
22
+ # ==============================
23
+ download_thread = None
24
+ download_log = []
25
+ cancel_download = False
26
+
27
+ # ==============================
28
+ # UTILS
29
+ # ==============================
30
+ def log(msg: str):
31
+ """Append a log message and return the full log as a string."""
32
+ global download_log
33
+ download_log.append(msg)
34
+ print(msg)
35
+ return "\n".join(download_log)
36
+
37
+ def test_huggingface_connectivity():
38
+ """Test if we can reach Hugging Face servers."""
39
+ try:
40
+ response = requests.get("https://huggingface.co/", timeout=10)
41
+ if response.status_code == 200:
42
+ return True, "✅ Can reach Hugging Face"
43
+ else:
44
+ return False, f"❌ Hugging Face returned status {response.status_code}"
45
+ except Exception as e:
46
+ return False, f"❌ Cannot reach Hugging Face: {str(e)}"
47
+
48
+ def is_model_ready():
49
+ """Check if required folders already exist."""
50
+ checkpoints = os.path.join(PERSISTENT_DIR, "checkpoints")
51
+ pretrained = os.path.join(PERSISTENT_DIR, "pretrained_models")
52
+ fashion = os.path.join(PERSISTENT_DIR, "fashion")
53
+ return all(os.path.exists(path) for path in [checkpoints, pretrained, fashion])
54
+
55
+ def get_directory_tree(root_dir, indent=""):
56
+ """Recursively build a directory tree string for logs."""
57
+ tree_str = ""
58
+ try:
59
+ items = sorted(os.listdir(root_dir))
60
+ except Exception as e:
61
+ return f"{indent}❌ [Error accessing {root_dir}]: {e}\n"
62
+
63
+ for i, item in enumerate(items):
64
+ path = os.path.join(root_dir, item)
65
+ connector = "└── " if i == len(items) - 1 else "├── "
66
+ tree_str += f"{indent}{connector}{item}\n"
67
+ if os.path.isdir(path):
68
+ tree_str += get_directory_tree(path, indent + (" " if i == len(items) - 1 else "│ "))
69
+ return tree_str
70
+
71
+ def download_individual_folders_with_retry():
72
+ """Alternative method with robust retry logic."""
73
+ try:
74
+ folders_to_download = ["checkpoints", "pretrained_models", "fashion"]
75
+ max_retries = 3
76
+
77
+ for folder in folders_to_download:
78
+ if cancel_download:
79
+ log("⛔ Download cancelled during individual folder download.")
80
+ return
81
+
82
+ folder_path = os.path.join(PERSISTENT_DIR, folder)
83
+ if os.path.exists(folder_path):
84
+ log(f"✅ Folder {folder} already exists, skipping...")
85
+ continue
86
+
87
+ for attempt in range(max_retries):
88
+ try:
89
+ log(f"⬇️ Downloading folder: {folder} (Attempt {attempt + 1}/{max_retries})")
90
+
91
+ snapshot_download(
92
+ repo_id=MODEL_REPO,
93
+ local_dir=folder_path,
94
+ resume_download=True,
95
+ local_dir_use_symlinks=False,
96
+ allow_patterns=f"{folder}/*"
97
+ )
98
+ log(f"✅ Successfully downloaded {folder}")
99
+ break
100
+
101
+ except Exception as e:
102
+ if attempt == max_retries - 1:
103
+ log(f"❌ Failed to download {folder} after {max_retries} attempts: {str(e)}")
104
+ else:
105
+ log(f"⚠️ Attempt {attempt + 1} failed for {folder}: {str(e)}")
106
+ import time
107
+ time.sleep(5)
108
+
109
+ log("✅ All folders processed.")
110
+
111
+ except Exception as e:
112
+ log(f"❌ Individual folder download failed: {str(e)}")
113
+
114
+ def download_models():
115
+ """Download models with logging and show directory tree after completion."""
116
+ global cancel_download
117
+
118
+ try:
119
+ # Test connectivity first
120
+ success, message = test_huggingface_connectivity()
121
+ log(message)
122
+ if not success:
123
+ log("🌐 Please check your internet connection and try again")
124
+ return
125
+
126
+ if is_model_ready():
127
+ log("✅ Models already downloaded. Skipping...")
128
+ log("📂 Directory structure:")
129
+ tree = get_directory_tree(PERSISTENT_DIR)
130
+ log(f"\n{tree}")
131
+ return
132
+
133
+ os.makedirs(PERSISTENT_DIR, exist_ok=True)
134
+
135
+ log("⬇️ Starting model download from Hugging Face Hub...")
136
+
137
+ # Add retry logic
138
+ max_retries = 3
139
+ for attempt in range(max_retries):
140
+ try:
141
+ if cancel_download:
142
+ log("⛔ Download cancelled during process.")
143
+ return
144
+
145
+ log(f"🔄 Attempt {attempt + 1}/{max_retries}")
146
+
147
+ snapshot_download(
148
+ repo_id=MODEL_REPO,
149
+ local_dir=PERSISTENT_DIR,
150
+ resume_download=True,
151
+ local_dir_use_symlinks=False,
152
+ )
153
+ break
154
+
155
+ except Exception as e:
156
+ if attempt == max_retries - 1:
157
+ raise e
158
+ log(f"⚠️ Attempt {attempt + 1} failed: {str(e)}")
159
+ import time
160
+ time.sleep(10)
161
+
162
+ if cancel_download:
163
+ log("⛔ Download cancelled during process.")
164
+ return
165
+
166
+ log("✅ Download completed successfully.")
167
+ log("📂 Listing downloaded directory structure...")
168
+ tree = get_directory_tree(PERSISTENT_DIR)
169
+ log(f"\n{tree}")
170
+
171
+ except Exception as e:
172
+ error_msg = str(e)
173
+ log(f"❌ Download failed after {max_retries} attempts: {error_msg}")
174
+ log("💡 Trying alternative download method...")
175
+ download_individual_folders_with_retry()
176
+
177
+ # ... rest of your code remains the same ...
178
+
179
+
180
+ def start_download():
181
+ """Start model download in a separate thread."""
182
+ global download_thread, download_log, cancel_download
183
+ if download_thread and download_thread.is_alive():
184
+ return "⚠️ Download already running..."
185
+
186
+ download_log = []
187
+ cancel_download = False
188
+ download_thread = threading.Thread(target=download_models)
189
+ download_thread.start()
190
+ return "📥 Download started..."
191
+
192
+ def get_download_status():
193
+ """Get the latest log status."""
194
+ global download_thread
195
+ status = "\n".join(download_log) if download_log else "Preparing download..."
196
+ if download_thread and download_thread.is_alive():
197
+ return status + "\n\n⏳ Download in progress..."
198
+ elif is_model_ready():
199
+ return status + "\n\n✅ Models are ready."
200
+ return status
201
+
202
+ def cancel_download_fn():
203
+ """Cancel request handler."""
204
+ global cancel_download
205
+ cancel_download = True
206
+ log("⛔ Download cancelled by user.")
207
+ return "Download cancelled."
208
+
209
+ # ==============================
210
+ # GRADIO UI
211
+ # ==============================
212
+ with gr.Blocks() as demo:
213
+ gr.Markdown("## 🧩 Model Downloader with Live Status & Persistent Storage")
214
+ gr.Markdown(f"**Model Source:** [{MODEL_REPO}](https://huggingface.co/{MODEL_REPO})")
215
+ gr.Markdown("**Required folders:** checkpoints/, pretrained_models/, fashion/")
216
+
217
+ with gr.Row():
218
+ start_btn = gr.Button("📥 Download Models")
219
+ cancel_btn = gr.Button("❌ Cancel Download")
220
+
221
+ status_box = gr.Textbox(
222
+ label="Download Logs",
223
+ lines=25,
224
+ interactive=False,
225
+ placeholder="Click 'Download Models' to start downloading..."
226
+ )
227
+
228
+ # Button bindings
229
+ start_btn.click(fn=start_download, inputs=None, outputs=status_box)
230
+ cancel_btn.click(fn=cancel_download_fn, inputs=None, outputs=status_box)
231
+
232
+ # Periodic refresh of logs
233
+ demo.load(fn=get_download_status, inputs=None, outputs=status_box, every=2)
234
+
235
+ # ==============================
236
+ # START APP
237
+ # ==============================
238
+ if __name__ == "__main__":
239
+ demo.launch()