aknapitsch user commited on
Commit
478d9e0
·
1 Parent(s): 19d7794

common video and image upload field

Browse files
Files changed (1) hide show
  1. app.py +243 -81
app.py CHANGED
@@ -19,8 +19,8 @@ import gradio as gr
19
  import numpy as np
20
  import spaces
21
  import torch
22
- from pillow_heif import register_heif_opener
23
  from PIL import Image
 
24
 
25
  register_heif_opener()
26
 
@@ -368,7 +368,7 @@ def populate_visualization_tabs(processed_data):
368
  # -------------------------------------------------------------------------
369
  # 2) Handle uploaded video/images --> produce target_dir + images
370
  # -------------------------------------------------------------------------
371
- def handle_uploads(input_video, input_images, s_time_interval=1.0):
372
  """
373
  Create a new 'target_dir' + 'images' subfolder, and place user-uploaded
374
  images or extracted frames from video into it. Return (target_dir, image_paths).
@@ -390,76 +390,100 @@ def handle_uploads(input_video, input_images, s_time_interval=1.0):
390
 
391
  image_paths = []
392
 
393
- # --- Handle images ---
394
- if input_images is not None:
395
- for file_data in input_images:
396
  if isinstance(file_data, dict) and "name" in file_data:
397
  file_path = file_data["name"]
398
  else:
399
- file_path = file_data
400
-
401
- # Check if the file is a HEIC image
402
  file_ext = os.path.splitext(file_path)[1].lower()
403
- if file_ext in ['.heic', '.heif']:
404
- # Convert HEIC to JPEG for better gallery compatibility
405
- try:
406
- with Image.open(file_path) as img:
407
- # Convert to RGB if necessary (HEIC can have different color modes)
408
- if img.mode not in ('RGB', 'L'):
409
- img = img.convert('RGB')
410
-
411
- # Create JPEG filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  base_name = os.path.splitext(os.path.basename(file_path))[0]
413
- dst_path = os.path.join(target_dir_images, f"{base_name}.jpg")
414
-
415
- # Save as JPEG with high quality
416
- img.save(dst_path, 'JPEG', quality=95)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  image_paths.append(dst_path)
418
- print(f"Converted HEIC to JPEG: {os.path.basename(file_path)} -> {os.path.basename(dst_path)}")
419
- except Exception as e:
420
- print(f"Error converting HEIC file {file_path}: {e}")
421
- # Fall back to copying as is
422
- dst_path = os.path.join(target_dir_images, os.path.basename(file_path))
423
  shutil.copy(file_path, dst_path)
424
  image_paths.append(dst_path)
425
- else:
426
- # Regular image files - copy as is
427
- dst_path = os.path.join(target_dir_images, os.path.basename(file_path))
428
- shutil.copy(file_path, dst_path)
429
- image_paths.append(dst_path)
430
-
431
- # --- Handle video ---
432
- if input_video is not None:
433
- if isinstance(input_video, dict) and "name" in input_video:
434
- video_path = input_video["name"]
435
- else:
436
- video_path = input_video
437
-
438
- vs = cv2.VideoCapture(video_path)
439
- fps = vs.get(cv2.CAP_PROP_FPS)
440
- frame_interval = int(fps * s_time_interval) # 1 frame/sec
441
-
442
- count = 0
443
- video_frame_num = 0
444
- while True:
445
- gotit, frame = vs.read()
446
- if not gotit:
447
- break
448
- count += 1
449
- if count % frame_interval == 0:
450
- image_path = os.path.join(
451
- target_dir_images, f"{video_frame_num:06}.png"
452
- )
453
- cv2.imwrite(image_path, frame)
454
- image_paths.append(image_path)
455
- video_frame_num += 1
456
 
457
  # Sort final images for gallery
458
  image_paths = sorted(image_paths)
459
 
460
  end_time = time.time()
461
  print(
462
- f"Files copied to {target_dir_images}; took {end_time - start_time:.3f} seconds"
463
  )
464
  return target_dir, image_paths
465
 
@@ -1058,8 +1082,14 @@ def load_example_scene(scene_name, examples_dir="examples"):
1058
  if selected_scene is None:
1059
  return None, None, None, "Scene not found"
1060
 
1061
- # Create target directory and copy images
1062
- target_dir, image_paths = handle_uploads(None, selected_scene["image_files"])
 
 
 
 
 
 
1063
 
1064
  return (
1065
  None, # Clear reconstruction output
@@ -1089,19 +1119,30 @@ with gr.Blocks(theme=theme, css=GRADIO_CSS) as demo:
1089
 
1090
  with gr.Row():
1091
  with gr.Column(scale=2):
1092
- input_video = gr.Video(label="Upload Video", interactive=True)
1093
- s_time_interval = gr.Slider(
1094
- minimum=0.1,
1095
- maximum=5.0,
1096
- value=1.0,
1097
- step=0.1,
1098
- label="Sample time interval (take a sample every x sec.)",
1099
  interactive=True,
1100
- visible=True,
1101
- )
1102
- input_images = gr.File(
1103
- file_count="multiple", label="Upload Images", interactive=True
1104
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1105
 
1106
  image_gallery = gr.Gallery(
1107
  label="Preview",
@@ -1112,6 +1153,13 @@ with gr.Blocks(theme=theme, css=GRADIO_CSS) as demo:
1112
  preview=True,
1113
  )
1114
 
 
 
 
 
 
 
 
1115
  with gr.Column(scale=4):
1116
  with gr.Column():
1117
  gr.Markdown(
@@ -1202,8 +1250,7 @@ with gr.Blocks(theme=theme, css=GRADIO_CSS) as demo:
1202
  submit_btn = gr.Button("Reconstruct", scale=1, variant="primary")
1203
  clear_btn = gr.ClearButton(
1204
  [
1205
- input_video,
1206
- input_images,
1207
  reconstruction_output,
1208
  log_output,
1209
  target_dir_output,
@@ -1422,15 +1469,130 @@ with gr.Blocks(theme=theme, css=GRADIO_CSS) as demo:
1422
  # -------------------------------------------------------------------------
1423
  # Auto-update gallery whenever user uploads or changes their files
1424
  # -------------------------------------------------------------------------
1425
- input_video.change(
1426
- fn=update_gallery_on_upload,
1427
- inputs=[input_video, input_images, s_time_interval],
1428
- outputs=[reconstruction_output, target_dir_output, image_gallery, log_output],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1429
  )
1430
- input_images.change(
1431
- fn=update_gallery_on_upload,
1432
- inputs=[input_video, input_images, s_time_interval],
1433
- outputs=[reconstruction_output, target_dir_output, image_gallery, log_output],
 
 
 
 
 
 
 
 
 
1434
  )
1435
 
1436
  # -------------------------------------------------------------------------
 
19
  import numpy as np
20
  import spaces
21
  import torch
 
22
  from PIL import Image
23
+ from pillow_heif import register_heif_opener
24
 
25
  register_heif_opener()
26
 
 
368
  # -------------------------------------------------------------------------
369
  # 2) Handle uploaded video/images --> produce target_dir + images
370
  # -------------------------------------------------------------------------
371
+ def handle_uploads(unified_upload, s_time_interval=1.0):
372
  """
373
  Create a new 'target_dir' + 'images' subfolder, and place user-uploaded
374
  images or extracted frames from video into it. Return (target_dir, image_paths).
 
390
 
391
  image_paths = []
392
 
393
+ # --- Handle uploaded files (both images and videos) ---
394
+ if unified_upload is not None:
395
+ for file_data in unified_upload:
396
  if isinstance(file_data, dict) and "name" in file_data:
397
  file_path = file_data["name"]
398
  else:
399
+ file_path = str(file_data)
400
+
 
401
  file_ext = os.path.splitext(file_path)[1].lower()
402
+
403
+ # Check if it's a video file
404
+ video_extensions = [
405
+ ".mp4",
406
+ ".avi",
407
+ ".mov",
408
+ ".mkv",
409
+ ".wmv",
410
+ ".flv",
411
+ ".webm",
412
+ ".m4v",
413
+ ".3gp",
414
+ ]
415
+ if file_ext in video_extensions:
416
+ # Handle as video
417
+ vs = cv2.VideoCapture(file_path)
418
+ fps = vs.get(cv2.CAP_PROP_FPS)
419
+ frame_interval = int(fps * s_time_interval) # frames per interval
420
+
421
+ count = 0
422
+ video_frame_num = 0
423
+ while True:
424
+ gotit, frame = vs.read()
425
+ if not gotit:
426
+ break
427
+ count += 1
428
+ if count % frame_interval == 0:
429
+ # Use original filename as prefix for frames
430
  base_name = os.path.splitext(os.path.basename(file_path))[0]
431
+ image_path = os.path.join(
432
+ target_dir_images, f"{base_name}_{video_frame_num:06}.png"
433
+ )
434
+ cv2.imwrite(image_path, frame)
435
+ image_paths.append(image_path)
436
+ video_frame_num += 1
437
+ vs.release()
438
+ print(
439
+ f"Extracted {video_frame_num} frames from video: {os.path.basename(file_path)}"
440
+ )
441
+
442
+ else:
443
+ # Handle as image
444
+ # Check if the file is a HEIC image
445
+ if file_ext in [".heic", ".heif"]:
446
+ # Convert HEIC to JPEG for better gallery compatibility
447
+ try:
448
+ with Image.open(file_path) as img:
449
+ # Convert to RGB if necessary (HEIC can have different color modes)
450
+ if img.mode not in ("RGB", "L"):
451
+ img = img.convert("RGB")
452
+
453
+ # Create JPEG filename
454
+ base_name = os.path.splitext(os.path.basename(file_path))[0]
455
+ dst_path = os.path.join(
456
+ target_dir_images, f"{base_name}.jpg"
457
+ )
458
+
459
+ # Save as JPEG with high quality
460
+ img.save(dst_path, "JPEG", quality=95)
461
+ image_paths.append(dst_path)
462
+ print(
463
+ f"Converted HEIC to JPEG: {os.path.basename(file_path)} -> {os.path.basename(dst_path)}"
464
+ )
465
+ except Exception as e:
466
+ print(f"Error converting HEIC file {file_path}: {e}")
467
+ # Fall back to copying as is
468
+ dst_path = os.path.join(
469
+ target_dir_images, os.path.basename(file_path)
470
+ )
471
+ shutil.copy(file_path, dst_path)
472
  image_paths.append(dst_path)
473
+ else:
474
+ # Regular image files - copy as is
475
+ dst_path = os.path.join(
476
+ target_dir_images, os.path.basename(file_path)
477
+ )
478
  shutil.copy(file_path, dst_path)
479
  image_paths.append(dst_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
 
481
  # Sort final images for gallery
482
  image_paths = sorted(image_paths)
483
 
484
  end_time = time.time()
485
  print(
486
+ f"Files processed to {target_dir_images}; took {end_time - start_time:.3f} seconds"
487
  )
488
  return target_dir, image_paths
489
 
 
1082
  if selected_scene is None:
1083
  return None, None, None, "Scene not found"
1084
 
1085
+ # Create file-like objects for the unified upload system
1086
+ # Convert image file paths to the format expected by unified_upload
1087
+ file_objects = []
1088
+ for image_path in selected_scene["image_files"]:
1089
+ file_objects.append(image_path)
1090
+
1091
+ # Create target directory and copy images using the unified upload system
1092
+ target_dir, image_paths = handle_uploads(file_objects, 1.0)
1093
 
1094
  return (
1095
  None, # Clear reconstruction output
 
1119
 
1120
  with gr.Row():
1121
  with gr.Column(scale=2):
1122
+ # Unified upload component for both videos and images
1123
+ unified_upload = gr.File(
1124
+ file_count="multiple",
1125
+ label="Upload Video or Images",
 
 
 
1126
  interactive=True,
1127
+ file_types=["image", "video"],
 
 
 
1128
  )
1129
+ with gr.Row():
1130
+ s_time_interval = gr.Slider(
1131
+ minimum=0.1,
1132
+ maximum=5.0,
1133
+ value=1.0,
1134
+ step=0.1,
1135
+ label="Video sample time interval (take a sample every x sec.)",
1136
+ interactive=True,
1137
+ visible=True,
1138
+ scale=3,
1139
+ )
1140
+ resample_btn = gr.Button(
1141
+ "Resample Video",
1142
+ visible=False,
1143
+ variant="secondary",
1144
+ scale=1,
1145
+ )
1146
 
1147
  image_gallery = gr.Gallery(
1148
  label="Preview",
 
1153
  preview=True,
1154
  )
1155
 
1156
+ clear_uploads_btn = gr.ClearButton(
1157
+ [unified_upload, image_gallery],
1158
+ value="Clear Uploads",
1159
+ variant="secondary",
1160
+ size="sm",
1161
+ )
1162
+
1163
  with gr.Column(scale=4):
1164
  with gr.Column():
1165
  gr.Markdown(
 
1250
  submit_btn = gr.Button("Reconstruct", scale=1, variant="primary")
1251
  clear_btn = gr.ClearButton(
1252
  [
1253
+ unified_upload,
 
1254
  reconstruction_output,
1255
  log_output,
1256
  target_dir_output,
 
1469
  # -------------------------------------------------------------------------
1470
  # Auto-update gallery whenever user uploads or changes their files
1471
  # -------------------------------------------------------------------------
1472
+ def update_gallery_on_unified_upload(files, interval):
1473
+ if not files:
1474
+ return None, None, None
1475
+ target_dir, image_paths = handle_uploads(files, interval)
1476
+ return (
1477
+ target_dir,
1478
+ image_paths,
1479
+ "Upload complete. Click 'Reconstruct' to begin 3D processing.",
1480
+ )
1481
+
1482
+ def show_resample_button(files):
1483
+ """Show the resample button only if there are uploaded files containing videos"""
1484
+ if not files:
1485
+ return gr.update(visible=False)
1486
+
1487
+ # Check if any uploaded files are videos
1488
+ video_extensions = [
1489
+ ".mp4",
1490
+ ".avi",
1491
+ ".mov",
1492
+ ".mkv",
1493
+ ".wmv",
1494
+ ".flv",
1495
+ ".webm",
1496
+ ".m4v",
1497
+ ".3gp",
1498
+ ]
1499
+ has_video = False
1500
+
1501
+ for file_data in files:
1502
+ if isinstance(file_data, dict) and "name" in file_data:
1503
+ file_path = file_data["name"]
1504
+ else:
1505
+ file_path = str(file_data)
1506
+
1507
+ file_ext = os.path.splitext(file_path)[1].lower()
1508
+ if file_ext in video_extensions:
1509
+ has_video = True
1510
+ break
1511
+
1512
+ return gr.update(visible=has_video)
1513
+
1514
+ def hide_resample_button():
1515
+ """Hide the resample button after use"""
1516
+ return gr.update(visible=False)
1517
+
1518
+ def resample_video_with_new_interval(files, new_interval, current_target_dir):
1519
+ """Resample video with new slider value"""
1520
+ if not files:
1521
+ return (
1522
+ current_target_dir,
1523
+ None,
1524
+ "No files to resample.",
1525
+ gr.update(visible=False),
1526
+ )
1527
+
1528
+ # Check if we have videos to resample
1529
+ video_extensions = [
1530
+ ".mp4",
1531
+ ".avi",
1532
+ ".mov",
1533
+ ".mkv",
1534
+ ".wmv",
1535
+ ".flv",
1536
+ ".webm",
1537
+ ".m4v",
1538
+ ".3gp",
1539
+ ]
1540
+ has_video = any(
1541
+ os.path.splitext(
1542
+ str(file_data["name"] if isinstance(file_data, dict) else file_data)
1543
+ )[1].lower()
1544
+ in video_extensions
1545
+ for file_data in files
1546
+ )
1547
+
1548
+ if not has_video:
1549
+ return (
1550
+ current_target_dir,
1551
+ None,
1552
+ "No videos found to resample.",
1553
+ gr.update(visible=False),
1554
+ )
1555
+
1556
+ # Clean up old target directory if it exists
1557
+ if (
1558
+ current_target_dir
1559
+ and current_target_dir != "None"
1560
+ and os.path.exists(current_target_dir)
1561
+ ):
1562
+ shutil.rmtree(current_target_dir)
1563
+
1564
+ # Process files with new interval
1565
+ target_dir, image_paths = handle_uploads(files, new_interval)
1566
+
1567
+ return (
1568
+ target_dir,
1569
+ image_paths,
1570
+ f"Video resampled with {new_interval}s interval. Click 'Reconstruct' to begin 3D processing.",
1571
+ gr.update(visible=False),
1572
+ )
1573
+
1574
+ unified_upload.change(
1575
+ fn=update_gallery_on_unified_upload,
1576
+ inputs=[unified_upload, s_time_interval],
1577
+ outputs=[target_dir_output, image_gallery, log_output],
1578
+ ).then(
1579
+ fn=show_resample_button,
1580
+ inputs=[unified_upload],
1581
+ outputs=[resample_btn],
1582
  )
1583
+
1584
+ # Show resample button when slider changes (only if files are uploaded)
1585
+ s_time_interval.change(
1586
+ fn=show_resample_button,
1587
+ inputs=[unified_upload],
1588
+ outputs=[resample_btn],
1589
+ )
1590
+
1591
+ # Handle resample button click
1592
+ resample_btn.click(
1593
+ fn=resample_video_with_new_interval,
1594
+ inputs=[unified_upload, s_time_interval, target_dir_output],
1595
+ outputs=[target_dir_output, image_gallery, log_output, resample_btn],
1596
  )
1597
 
1598
  # -------------------------------------------------------------------------