ColamanAI commited on
Commit
c5c9c70
·
verified ·
1 Parent(s): 0efdb24

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -57
app.py CHANGED
@@ -682,6 +682,7 @@ def run_model(
682
  filter_white_bg=False,
683
  enable_segmentation=False,
684
  text_prompt=DEFAULT_TEXT_PROMPT,
 
685
  ):
686
  """
687
  Run the MapAnything model + optional segmentation
@@ -689,12 +690,14 @@ def run_model(
689
  global model
690
  import torch
691
 
 
692
  print(f"Processing images from {target_dir}")
693
 
694
  device = "cuda" if torch.cuda.is_available() else "cpu"
695
  device = torch.device(device)
696
 
697
  # Initialize MapAnything model
 
698
  if model is None:
699
  model = initialize_mapanything_model(high_level_config, device)
700
  else:
@@ -704,10 +707,12 @@ def run_model(
704
 
705
  # Load segmentation models if enabled
706
  if enable_segmentation:
 
707
  load_grounding_dino_model(device)
708
  load_sam_model(device)
709
 
710
  # Load images
 
711
  print("Loading images...")
712
  image_folder_path = os.path.join(target_dir, "images")
713
  views = load_images(image_folder_path)
@@ -717,12 +722,14 @@ def run_model(
717
  raise ValueError("No images found. Check your upload.")
718
 
719
  # Run model inference
 
720
  print("Running inference...")
721
  outputs = model.infer(
722
  views, apply_mask=apply_mask, mask_edges=True, memory_efficient_inference=False
723
  )
724
 
725
  # Convert predictions
 
726
  predictions = {}
727
  extrinsic_list = []
728
  intrinsic_list = []
@@ -768,6 +775,7 @@ def run_model(
768
  predictions["final_mask"] = np.stack(final_mask_list, axis=0)
769
 
770
  # Process visualization data
 
771
  processed_data = process_predictions_for_visualization(
772
  predictions, views, high_level_config, filter_black_bg, filter_white_bg
773
  )
@@ -775,6 +783,7 @@ def run_model(
775
  # Segmentation processing
776
  segmented_glb = None
777
  if enable_segmentation and grounding_dino_model is not None:
 
778
  print("\n🎯 Starting segmentation...")
779
  print(f"🔍 Detection prompt: {text_prompt[:100]}...")
780
 
@@ -782,6 +791,8 @@ def run_model(
782
  all_view_masks = []
783
 
784
  for view_idx, ref_image in enumerate(images_list):
 
 
785
  print(f"\n📸 Processing view {view_idx + 1}/{len(images_list)}...")
786
 
787
  if ref_image.dtype != np.uint8:
@@ -810,17 +821,21 @@ def run_model(
810
 
811
  # Match objects across views
812
  if any(len(dets) > 0 for dets in all_view_detections):
 
813
  object_id_map, unique_objects = match_objects_across_views(all_view_detections)
814
 
815
  # Generate segmented mesh
 
816
  segmented_glb = create_multi_view_segmented_mesh(
817
  processed_data, all_view_detections, all_view_masks,
818
  object_id_map, unique_objects, target_dir
819
  )
820
 
821
  # Cleanup
 
822
  torch.cuda.empty_cache()
823
 
 
824
  return predictions, processed_data, segmented_glb
825
 
826
 
@@ -1079,17 +1094,17 @@ def gradio_demo(
1079
  show_cam=True,
1080
  filter_black_bg=False,
1081
  filter_white_bg=False,
1082
- conf_thres=3.0,
1083
  apply_mask=True,
1084
  show_mesh=True,
1085
  enable_segmentation=False,
1086
  text_prompt=DEFAULT_TEXT_PROMPT,
1087
- use_sam=True,
1088
  ):
1089
  """执行重建"""
1090
  if not os.path.isdir(target_dir) or target_dir == "None":
1091
  return None, None, "❌ 未找到有效的目标目录,请先上传文件", None, None, None, None, None, None, None, None, None
1092
 
 
1093
  start_time = time.time()
1094
  gc.collect()
1095
  torch.cuda.empty_cache()
@@ -1103,14 +1118,16 @@ def gradio_demo(
1103
  all_files = [f"{i}: {filename}" for i, filename in enumerate(all_files)]
1104
  frame_filter_choices = ["All"] + all_files
1105
 
 
1106
  print("运行 MapAnything 模型...")
1107
  with torch.no_grad():
1108
  predictions, processed_data, segmented_glb = run_model(
1109
  target_dir, apply_mask, True, filter_black_bg, filter_white_bg,
1110
- enable_segmentation, text_prompt
1111
  )
1112
 
1113
  # 保存预测结果
 
1114
  prediction_save_path = os.path.join(target_dir, "predictions.npz")
1115
  np.savez(prediction_save_path, **predictions)
1116
 
@@ -1118,6 +1135,7 @@ def gradio_demo(
1118
  frame_filter = "All"
1119
 
1120
  # 生成 GLB 文件名
 
1121
  glbfile = os.path.join(
1122
  target_dir,
1123
  f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}.glb",
@@ -1131,20 +1149,21 @@ def gradio_demo(
1131
  mask_black_bg=filter_black_bg,
1132
  mask_white_bg=filter_white_bg,
1133
  as_mesh=show_mesh,
1134
- conf_percentile=conf_thres,
1135
  )
1136
  glbscene.export(file_obj=glbfile)
1137
 
1138
  # 清理内存
 
1139
  del predictions
1140
  gc.collect()
1141
  torch.cuda.empty_cache()
1142
 
1143
  end_time = time.time()
1144
  print(f"总耗时: {end_time - start_time:.2f}秒")
1145
- log_msg = f"✅ 重建成功 ({len(all_files)} 帧)"
1146
 
1147
  # Populate visualization tabs
 
1148
  depth_vis, normal_vis, measure_img, measure_pts = populate_visualization_tabs(
1149
  processed_data
1150
  )
@@ -1153,6 +1172,8 @@ def gradio_demo(
1153
  depth_selector, normal_selector, measure_selector = update_view_selectors(
1154
  processed_data
1155
  )
 
 
1156
 
1157
  return (
1158
  glbfile,
@@ -1428,7 +1449,6 @@ def update_visualization(
1428
  frame_filter,
1429
  show_cam,
1430
  is_example,
1431
- conf_thres=None,
1432
  filter_black_bg=False,
1433
  filter_white_bg=False,
1434
  show_mesh=True,
@@ -1459,7 +1479,6 @@ def update_visualization(
1459
  mask_black_bg=filter_black_bg,
1460
  mask_white_bg=filter_white_bg,
1461
  as_mesh=show_mesh,
1462
- conf_percentile=conf_thres,
1463
  )
1464
  glbscene.export(file_obj=glbfile)
1465
 
@@ -1578,7 +1597,7 @@ def load_example_scene(scene_name, examples_dir="examples"):
1578
  break
1579
 
1580
  if selected_scene is None:
1581
- return None, None, None, None, "❌ 场景未找到"
1582
 
1583
  file_objects = []
1584
  for image_path in selected_scene["image_files"]:
@@ -1587,7 +1606,6 @@ def load_example_scene(scene_name, examples_dir="examples"):
1587
  target_dir, image_paths = handle_uploads(file_objects, 1.0)
1588
 
1589
  return (
1590
- None,
1591
  None,
1592
  target_dir,
1593
  image_paths,
@@ -1656,35 +1674,45 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
1656
  with gr.Column(scale=1, min_width=300):
1657
  gr.Markdown("### 📤 输入")
1658
 
1659
- with gr.Tabs():
1660
- with gr.Tab("📷 图片"):
1661
- input_images = gr.File(
1662
- file_count="multiple",
1663
- label="上传多张图片(推荐3-10张)",
1664
- interactive=True,
1665
- height=200
1666
- )
1667
-
1668
- with gr.Tab("🎥 视频"):
1669
- input_video = gr.Video(
1670
- label="上传视频",
1671
- interactive=True,
1672
- height=300
1673
- )
1674
- s_time_interval = gr.Slider(
1675
- minimum=0.1, maximum=5.0, value=1.0, step=0.1,
1676
- label="帧采样间隔(秒)", interactive=True
1677
- )
 
 
1678
 
1679
  image_gallery = gr.Gallery(
1680
  label="图片预览", columns=3, height=350,
1681
  show_download_button=True, object_fit="contain", preview=True
1682
  )
1683
 
 
 
 
 
 
 
 
 
1684
  with gr.Row():
1685
  submit_btn = gr.Button("🚀 开始重建", variant="primary", scale=2)
1686
  clear_btn = gr.ClearButton(
1687
- [input_video, input_images, target_dir_output, image_gallery],
1688
  value="🗑️ 清空", scale=1
1689
  )
1690
 
@@ -1755,18 +1783,14 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
1755
  max_lines=1
1756
  )
1757
 
1758
- # 高级选项(可折叠
1759
- with gr.Accordion("⚙️ 高级选项", open=False):
1760
  with gr.Row(equal_height=False):
1761
  with gr.Column(scale=1, min_width=300):
1762
  gr.Markdown("#### 可视化参数")
1763
  frame_filter = gr.Dropdown(
1764
  choices=["All"], value="All", label="显示帧"
1765
  )
1766
- conf_thres = gr.Slider(
1767
- minimum=0, maximum=100, value=0, step=0.1,
1768
- label="置信度阈值(百分位)"
1769
- )
1770
  show_cam = gr.Checkbox(label="显示相机", value=True)
1771
  show_mesh = gr.Checkbox(label="显示网格", value=True)
1772
  filter_black_bg = gr.Checkbox(label="过滤黑色背景", value=False)
@@ -1821,7 +1845,7 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
1821
  scene_img.select(
1822
  fn=lambda name=scene["name"]: load_example_scene(name),
1823
  outputs=[
1824
- reconstruction_output, segmented_output,
1825
  target_dir_output, image_gallery, log_output
1826
  ]
1827
  )
@@ -1839,32 +1863,110 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
1839
  )
1840
 
1841
  # 上传文件自动更新
1842
- def update_gallery_on_unified_upload(files_video, files_images, interval):
1843
- if not files_video and not files_images:
1844
- return None, None, None, None
1845
- # Combine both inputs
1846
- all_files = []
1847
- if files_video:
1848
- all_files.append(files_video)
1849
- if files_images:
1850
- all_files.extend(files_images)
1851
- target_dir, image_paths = handle_uploads(all_files, interval)
1852
  return (
1853
- None,
1854
  target_dir,
1855
  image_paths,
1856
  "✅ 上传完成,点击「开始重建」进行 3D 处理",
1857
  )
1858
 
1859
- input_video.change(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1860
  fn=update_gallery_on_unified_upload,
1861
- inputs=[input_video, input_images, s_time_interval],
1862
- outputs=[segmented_output, target_dir_output, image_gallery, log_output]
 
 
 
 
1863
  )
1864
- input_images.change(
1865
- fn=update_gallery_on_unified_upload,
1866
- inputs=[input_video, input_images, s_time_interval],
1867
- outputs=[segmented_output, target_dir_output, image_gallery, log_output]
 
 
 
 
 
 
 
 
 
1868
  )
1869
 
1870
  # 重建按钮
@@ -1878,7 +1980,7 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
1878
  fn=gradio_demo,
1879
  inputs=[
1880
  target_dir_output, frame_filter, show_cam,
1881
- filter_black_bg, filter_white_bg, conf_thres,
1882
  apply_mask_checkbox, show_mesh,
1883
  enable_segmentation, text_prompt
1884
  ],
@@ -1896,12 +1998,12 @@ with gr.Blocks(theme=theme, css=CUSTOM_CSS, title="MapAnything V2 - 3D重建与
1896
  clear_btn.add([reconstruction_output, segmented_output, log_output])
1897
 
1898
  # 可视化参数实时更新
1899
- for component in [frame_filter, show_cam, conf_thres, show_mesh]:
1900
  component.change(
1901
  fn=update_visualization,
1902
  inputs=[
1903
  target_dir_output, frame_filter, show_cam, is_example,
1904
- conf_thres, filter_black_bg, filter_white_bg, show_mesh
1905
  ],
1906
  outputs=[reconstruction_output, log_output]
1907
  )
 
682
  filter_white_bg=False,
683
  enable_segmentation=False,
684
  text_prompt=DEFAULT_TEXT_PROMPT,
685
+ progress=gr.Progress(),
686
  ):
687
  """
688
  Run the MapAnything model + optional segmentation
 
690
  global model
691
  import torch
692
 
693
+ progress(0, desc="🔧 初始化设备...")
694
  print(f"Processing images from {target_dir}")
695
 
696
  device = "cuda" if torch.cuda.is_available() else "cpu"
697
  device = torch.device(device)
698
 
699
  # Initialize MapAnything model
700
+ progress(0.05, desc="📥 加载 MapAnything 模型...")
701
  if model is None:
702
  model = initialize_mapanything_model(high_level_config, device)
703
  else:
 
707
 
708
  # Load segmentation models if enabled
709
  if enable_segmentation:
710
+ progress(0.1, desc="🎯 加载分割模型...")
711
  load_grounding_dino_model(device)
712
  load_sam_model(device)
713
 
714
  # Load images
715
+ progress(0.15, desc="📷 加载图片...")
716
  print("Loading images...")
717
  image_folder_path = os.path.join(target_dir, "images")
718
  views = load_images(image_folder_path)
 
722
  raise ValueError("No images found. Check your upload.")
723
 
724
  # Run model inference
725
+ progress(0.2, desc=f"🚀 运行 3D 重建 ({len(views)} 张图片)...")
726
  print("Running inference...")
727
  outputs = model.infer(
728
  views, apply_mask=apply_mask, mask_edges=True, memory_efficient_inference=False
729
  )
730
 
731
  # Convert predictions
732
+ progress(0.5, desc="🔄 处理预测结果...")
733
  predictions = {}
734
  extrinsic_list = []
735
  intrinsic_list = []
 
775
  predictions["final_mask"] = np.stack(final_mask_list, axis=0)
776
 
777
  # Process visualization data
778
+ progress(0.6, desc="🎨 准备可视化数据...")
779
  processed_data = process_predictions_for_visualization(
780
  predictions, views, high_level_config, filter_black_bg, filter_white_bg
781
  )
 
783
  # Segmentation processing
784
  segmented_glb = None
785
  if enable_segmentation and grounding_dino_model is not None:
786
+ progress(0.65, desc="🎯 开始物体分割...")
787
  print("\n🎯 Starting segmentation...")
788
  print(f"🔍 Detection prompt: {text_prompt[:100]}...")
789
 
 
791
  all_view_masks = []
792
 
793
  for view_idx, ref_image in enumerate(images_list):
794
+ progress(0.65 + (view_idx / len(images_list)) * 0.2,
795
+ desc=f"🔍 检测视图 {view_idx + 1}/{len(images_list)}...")
796
  print(f"\n📸 Processing view {view_idx + 1}/{len(images_list)}...")
797
 
798
  if ref_image.dtype != np.uint8:
 
821
 
822
  # Match objects across views
823
  if any(len(dets) > 0 for dets in all_view_detections):
824
+ progress(0.85, desc="🔗 匹配跨视图物体...")
825
  object_id_map, unique_objects = match_objects_across_views(all_view_detections)
826
 
827
  # Generate segmented mesh
828
+ progress(0.9, desc="🏗️ 生成分割3D模型...")
829
  segmented_glb = create_multi_view_segmented_mesh(
830
  processed_data, all_view_detections, all_view_masks,
831
  object_id_map, unique_objects, target_dir
832
  )
833
 
834
  # Cleanup
835
+ progress(0.95, desc="🧹 清理内存...")
836
  torch.cuda.empty_cache()
837
 
838
+ progress(1.0, desc="✅ 完成!")
839
  return predictions, processed_data, segmented_glb
840
 
841
 
 
1094
  show_cam=True,
1095
  filter_black_bg=False,
1096
  filter_white_bg=False,
 
1097
  apply_mask=True,
1098
  show_mesh=True,
1099
  enable_segmentation=False,
1100
  text_prompt=DEFAULT_TEXT_PROMPT,
1101
+ progress=gr.Progress(),
1102
  ):
1103
  """执行重建"""
1104
  if not os.path.isdir(target_dir) or target_dir == "None":
1105
  return None, None, "❌ 未找到有效的目标目录,请先上传文件", None, None, None, None, None, None, None, None, None
1106
 
1107
+ progress(0, desc="🔄 准备重建...")
1108
  start_time = time.time()
1109
  gc.collect()
1110
  torch.cuda.empty_cache()
 
1118
  all_files = [f"{i}: {filename}" for i, filename in enumerate(all_files)]
1119
  frame_filter_choices = ["All"] + all_files
1120
 
1121
+ progress(0.05, desc="🚀 运行 MapAnything 模型...")
1122
  print("运行 MapAnything 模型...")
1123
  with torch.no_grad():
1124
  predictions, processed_data, segmented_glb = run_model(
1125
  target_dir, apply_mask, True, filter_black_bg, filter_white_bg,
1126
+ enable_segmentation, text_prompt, progress
1127
  )
1128
 
1129
  # 保存预测结果
1130
+ progress(0.92, desc="💾 保存预测结果...")
1131
  prediction_save_path = os.path.join(target_dir, "predictions.npz")
1132
  np.savez(prediction_save_path, **predictions)
1133
 
 
1135
  frame_filter = "All"
1136
 
1137
  # 生成 GLB 文件名
1138
+ progress(0.93, desc="🏗️ 生成原始3D模型...")
1139
  glbfile = os.path.join(
1140
  target_dir,
1141
  f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}.glb",
 
1149
  mask_black_bg=filter_black_bg,
1150
  mask_white_bg=filter_white_bg,
1151
  as_mesh=show_mesh,
 
1152
  )
1153
  glbscene.export(file_obj=glbfile)
1154
 
1155
  # 清理内存
1156
+ progress(0.96, desc="🧹 清理内存...")
1157
  del predictions
1158
  gc.collect()
1159
  torch.cuda.empty_cache()
1160
 
1161
  end_time = time.time()
1162
  print(f"总耗时: {end_time - start_time:.2f}秒")
1163
+ log_msg = f"✅ 重建成功 ({len(all_files)} 帧,耗时 {end_time - start_time:.1f}秒)"
1164
 
1165
  # Populate visualization tabs
1166
+ progress(0.98, desc="🎨 生成可视化...")
1167
  depth_vis, normal_vis, measure_img, measure_pts = populate_visualization_tabs(
1168
  processed_data
1169
  )
 
1172
  depth_selector, normal_selector, measure_selector = update_view_selectors(
1173
  processed_data
1174
  )
1175
+
1176
+ progress(1.0, desc="✅ 全部完成!")
1177
 
1178
  return (
1179
  glbfile,
 
1449
  frame_filter,
1450
  show_cam,
1451
  is_example,
 
1452
  filter_black_bg=False,
1453
  filter_white_bg=False,
1454
  show_mesh=True,
 
1479
  mask_black_bg=filter_black_bg,
1480
  mask_white_bg=filter_white_bg,
1481
  as_mesh=show_mesh,
 
1482
  )
1483
  glbscene.export(file_obj=glbfile)
1484
 
 
1597
  break
1598
 
1599
  if selected_scene is None:
1600
+ return None, None, None, "❌ 场景未找到"
1601
 
1602
  file_objects = []
1603
  for image_path in selected_scene["image_files"]:
 
1606
  target_dir, image_paths = handle_uploads(file_objects, 1.0)
1607
 
1608
  return (
 
1609
  None,
1610
  target_dir,
1611
  image_paths,
 
1674
  with gr.Column(scale=1, min_width=300):
1675
  gr.Markdown("### 📤 输入")
1676
 
1677
+ unified_upload = gr.File(
1678
+ file_count="multiple",
1679
+ label="上传视频或图片",
1680
+ interactive=True,
1681
+ file_types=["image", "video"],
1682
+ )
1683
+
1684
+ with gr.Row():
1685
+ s_time_interval = gr.Slider(
1686
+ minimum=0.1, maximum=5.0, value=1.0, step=0.1,
1687
+ label="视频采样时间间隔(每x秒取一帧)",
1688
+ interactive=True,
1689
+ visible=True,
1690
+ scale=3,
1691
+ )
1692
+ resample_btn = gr.Button(
1693
+ "重新采样视频",
1694
+ visible=False,
1695
+ variant="secondary",
1696
+ scale=1,
1697
+ )
1698
 
1699
  image_gallery = gr.Gallery(
1700
  label="图片预览", columns=3, height=350,
1701
  show_download_button=True, object_fit="contain", preview=True
1702
  )
1703
 
1704
+
1705
+ clear_uploads_btn = gr.ClearButton(
1706
+ [unified_upload, image_gallery],
1707
+ value="清空上传",
1708
+ variant="secondary",
1709
+ size="sm",
1710
+ )
1711
+
1712
  with gr.Row():
1713
  submit_btn = gr.Button("🚀 开始重建", variant="primary", scale=2)
1714
  clear_btn = gr.ClearButton(
1715
+ [unified_upload, target_dir_output, image_gallery],
1716
  value="🗑️ 清空", scale=1
1717
  )
1718
 
 
1783
  max_lines=1
1784
  )
1785
 
1786
+ # 高级选项(默认打开
1787
+ with gr.Accordion("⚙️ 高级选项", open=True):
1788
  with gr.Row(equal_height=False):
1789
  with gr.Column(scale=1, min_width=300):
1790
  gr.Markdown("#### 可视化参数")
1791
  frame_filter = gr.Dropdown(
1792
  choices=["All"], value="All", label="显示帧"
1793
  )
 
 
 
 
1794
  show_cam = gr.Checkbox(label="显示相机", value=True)
1795
  show_mesh = gr.Checkbox(label="显示网格", value=True)
1796
  filter_black_bg = gr.Checkbox(label="过滤黑色背景", value=False)
 
1845
  scene_img.select(
1846
  fn=lambda name=scene["name"]: load_example_scene(name),
1847
  outputs=[
1848
+ reconstruction_output,
1849
  target_dir_output, image_gallery, log_output
1850
  ]
1851
  )
 
1863
  )
1864
 
1865
  # 上传文件自动更新
1866
+ def update_gallery_on_unified_upload(files, interval):
1867
+ if not files:
1868
+ return None, None, None
1869
+ target_dir, image_paths = handle_uploads(files, interval)
 
 
 
 
 
 
1870
  return (
 
1871
  target_dir,
1872
  image_paths,
1873
  "✅ 上传完成,点击「开始重建」进行 3D 处理",
1874
  )
1875
 
1876
+ def show_resample_button(files):
1877
+ """仅当上传的文件包含视频时显示重新采样按钮"""
1878
+ if not files:
1879
+ return gr.update(visible=False)
1880
+
1881
+ # 检查是否有视频文件
1882
+ video_extensions = [
1883
+ ".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp",
1884
+ ]
1885
+ has_video = False
1886
+
1887
+ for file_data in files:
1888
+ if isinstance(file_data, dict) and "name" in file_data:
1889
+ file_path = file_data["name"]
1890
+ else:
1891
+ file_path = str(file_data)
1892
+
1893
+ file_ext = os.path.splitext(file_path)[1].lower()
1894
+ if file_ext in video_extensions:
1895
+ has_video = True
1896
+ break
1897
+
1898
+ return gr.update(visible=has_video)
1899
+
1900
+ def resample_video_with_new_interval(files, new_interval, current_target_dir):
1901
+ """使用新的滑块值重新采样视频"""
1902
+ if not files:
1903
+ return (
1904
+ current_target_dir,
1905
+ None,
1906
+ "没有可重新采样的文件。",
1907
+ gr.update(visible=False),
1908
+ )
1909
+
1910
+ # 检查是否有视频需要重新采样
1911
+ video_extensions = [
1912
+ ".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp",
1913
+ ]
1914
+ has_video = any(
1915
+ os.path.splitext(
1916
+ str(file_data["name"] if isinstance(file_data, dict) else file_data)
1917
+ )[1].lower()
1918
+ in video_extensions
1919
+ for file_data in files
1920
+ )
1921
+
1922
+ if not has_video:
1923
+ return (
1924
+ current_target_dir,
1925
+ None,
1926
+ "未找到视频进行重新采样。",
1927
+ gr.update(visible=False),
1928
+ )
1929
+
1930
+ # 清理旧的目标目录
1931
+ if (
1932
+ current_target_dir
1933
+ and current_target_dir != "None"
1934
+ and os.path.exists(current_target_dir)
1935
+ ):
1936
+ shutil.rmtree(current_target_dir)
1937
+
1938
+ # 使用新间隔处理文件
1939
+ target_dir, image_paths = handle_uploads(files, new_interval)
1940
+
1941
+ return (
1942
+ target_dir,
1943
+ image_paths,
1944
+ f"视频已使用 {new_interval}秒 间隔重新采样。点击「开始重建」进行 3D 处理。",
1945
+ gr.update(visible=False),
1946
+ )
1947
+
1948
+ unified_upload.change(
1949
  fn=update_gallery_on_unified_upload,
1950
+ inputs=[unified_upload, s_time_interval],
1951
+ outputs=[target_dir_output, image_gallery, log_output]
1952
+ ).then(
1953
+ fn=show_resample_button,
1954
+ inputs=[unified_upload],
1955
+ outputs=[resample_btn],
1956
  )
1957
+
1958
+ # 滑块改变时显示重新采样按钮(仅当已上传文件时)
1959
+ s_time_interval.change(
1960
+ fn=show_resample_button,
1961
+ inputs=[unified_upload],
1962
+ outputs=[resample_btn],
1963
+ )
1964
+
1965
+ # 处理重新采样按钮点击
1966
+ resample_btn.click(
1967
+ fn=resample_video_with_new_interval,
1968
+ inputs=[unified_upload, s_time_interval, target_dir_output],
1969
+ outputs=[target_dir_output, image_gallery, log_output, resample_btn],
1970
  )
1971
 
1972
  # 重建按钮
 
1980
  fn=gradio_demo,
1981
  inputs=[
1982
  target_dir_output, frame_filter, show_cam,
1983
+ filter_black_bg, filter_white_bg,
1984
  apply_mask_checkbox, show_mesh,
1985
  enable_segmentation, text_prompt
1986
  ],
 
1998
  clear_btn.add([reconstruction_output, segmented_output, log_output])
1999
 
2000
  # 可视化参数实时更新
2001
+ for component in [frame_filter, show_cam, show_mesh]:
2002
  component.change(
2003
  fn=update_visualization,
2004
  inputs=[
2005
  target_dir_output, frame_filter, show_cam, is_example,
2006
+ filter_black_bg, filter_white_bg, show_mesh
2007
  ],
2008
  outputs=[reconstruction_output, log_output]
2009
  )