prithivMLmods commited on
Commit
c4fdfd9
Β·
verified Β·
1 Parent(s): dea127c

update app

Browse files
Files changed (1) hide show
  1. app.py +437 -171
app.py CHANGED
@@ -111,8 +111,37 @@ class OrangeRedTheme(Soft):
111
  block_label_background_fill="*primary_200",
112
  )
113
 
 
114
  orange_red_theme = OrangeRedTheme()
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  high_level_config = {
117
  "path": "configs/train.yaml",
118
  "hf_model_name": "facebook/map-anything-v1",
@@ -136,6 +165,289 @@ model = None
136
  TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
137
  os.makedirs(TMP_DIR, exist_ok=True)
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  def predictions_to_rrd(predictions, glbfile, target_dir, frame_filter="All", show_cam=True):
140
  run_id = str(uuid.uuid4())
141
  timestamp = datetime.now().strftime("%Y-%m-%dT%H%M%S")
@@ -292,7 +604,6 @@ def run_model(target_dir, apply_mask=True, mask_edges=True, filter_black_bg=Fals
292
  torch.cuda.empty_cache()
293
  return predictions, processed_data
294
 
295
-
296
  def update_view_selectors(processed_data):
297
  choices = [f"View {i + 1}" for i in range(len(processed_data))] if processed_data else ["View 1"]
298
  return (
@@ -337,7 +648,11 @@ def update_measure_view(processed_data, view_index):
337
  overlay_color = np.array([255, 220, 220], dtype=np.uint8)
338
  alpha = 0.5
339
  for c in range(3):
340
- image[:, :, c] = np.where(invalid_mask, (1 - alpha) * image[:, :, c] + alpha * overlay_color[c], image[:, :, c]).astype(np.uint8)
 
 
 
 
341
  return image, []
342
 
343
 
@@ -346,7 +661,7 @@ def navigate_depth_view(processed_data, current_selector_value, direction):
346
  return "View 1", None
347
  try:
348
  current_view = int(current_selector_value.split()[1]) - 1
349
- except:
350
  current_view = 0
351
  new_view = (current_view + direction) % len(processed_data)
352
  return f"View {new_view + 1}", update_depth_view(processed_data, new_view)
@@ -357,7 +672,7 @@ def navigate_normal_view(processed_data, current_selector_value, direction):
357
  return "View 1", None
358
  try:
359
  current_view = int(current_selector_value.split()[1]) - 1
360
- except:
361
  current_view = 0
362
  new_view = (current_view + direction) % len(processed_data)
363
  return f"View {new_view + 1}", update_normal_view(processed_data, new_view)
@@ -368,7 +683,7 @@ def navigate_measure_view(processed_data, current_selector_value, direction):
368
  return "View 1", None, []
369
  try:
370
  current_view = int(current_selector_value.split()[1]) - 1
371
- except:
372
  current_view = 0
373
  new_view = (current_view + direction) % len(processed_data)
374
  measure_image, measure_points = update_measure_view(processed_data, new_view)
@@ -378,7 +693,13 @@ def navigate_measure_view(processed_data, current_selector_value, direction):
378
  def populate_visualization_tabs(processed_data):
379
  if not processed_data:
380
  return None, None, None, []
381
- return update_depth_view(processed_data, 0), update_normal_view(processed_data, 0), update_measure_view(processed_data, 0)[0], []
 
 
 
 
 
 
382
 
383
  def handle_uploads(unified_upload, s_time_interval=1.0):
384
  start_time = time.time()
@@ -469,7 +790,14 @@ def gradio_demo(target_dir, frame_filter="All", show_cam=True, filter_black_bg=F
469
  target_dir,
470
  f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}_black{filter_black_bg}_white{filter_white_bg}.glb",
471
  )
472
- glbscene = predictions_to_glb(predictions, filter_by_frames=frame_filter, show_cam=show_cam, mask_black_bg=filter_black_bg, mask_white_bg=filter_white_bg, as_mesh=show_mesh)
 
 
 
 
 
 
 
473
  glbscene.export(file_obj=glbfile)
474
 
475
  rrd_path = predictions_to_rrd(predictions, glbfile, target_dir, frame_filter, show_cam)
@@ -529,7 +857,11 @@ def process_predictions_for_visualization(predictions, views, high_level_config,
529
  mask = mask & (view_colors.sum(axis=2) >= 16)
530
  if filter_white_bg:
531
  view_colors = image[0] * 255 if image[0].max() <= 1.0 else image[0]
532
- mask = mask & ~((view_colors[:, :, 0] > 240) & (view_colors[:, :, 1] > 240) & (view_colors[:, :, 2] > 240))
 
 
 
 
533
  normals, _ = points_to_normals(pred_pts3d, mask=mask)
534
  processed_data[view_idx] = {
535
  "image": image[0],
@@ -547,7 +879,7 @@ def measure(processed_data, measure_points, current_view_selector, event: gr.Sel
547
  return None, [], "No data available"
548
  try:
549
  current_view_index = int(current_view_selector.split()[1]) - 1
550
- except:
551
  current_view_index = 0
552
  current_view_index = max(0, min(current_view_index, len(processed_data) - 1))
553
  current_view = processed_data[list(processed_data.keys())[current_view_index]]
@@ -555,7 +887,11 @@ def measure(processed_data, measure_points, current_view_selector, event: gr.Sel
555
  return None, [], "No view data available"
556
 
557
  point2d = event.index[0], event.index[1]
558
- if current_view["mask"] is not None and 0 <= point2d[1] < current_view["mask"].shape[0] and 0 <= point2d[0] < current_view["mask"].shape[1]:
 
 
 
 
559
  if not current_view["mask"][point2d[1], point2d[0]]:
560
  masked_image, _ = update_measure_view(processed_data, current_view_index)
561
  return masked_image, measure_points, '<span style="color: red; font-weight: bold;">Cannot measure on masked areas</span>'
@@ -575,19 +911,38 @@ def measure(processed_data, measure_points, current_view_selector, event: gr.Sel
575
 
576
  depth_text = ""
577
  for i, p in enumerate(measure_points):
578
- if current_view["depth"] is not None and 0 <= p[1] < current_view["depth"].shape[0] and 0 <= p[0] < current_view["depth"].shape[1]:
 
 
 
 
579
  depth_text += f"- **P{i + 1} depth: {current_view['depth'][p[1], p[0]]:.2f}m**\n"
580
- elif points3d is not None and 0 <= p[1] < points3d.shape[0] and 0 <= p[0] < points3d.shape[1]:
 
 
 
 
581
  depth_text += f"- **P{i + 1} Z-coord: {points3d[p[1], p[0], 2]:.2f}m**\n"
582
 
583
  if len(measure_points) == 2:
584
  point1, point2 = measure_points
585
- if all(0 <= point1[0] < image.shape[1] and 0 <= point1[1] < image.shape[0] and 0 <= point2[0] < image.shape[1] and 0 <= point2[1] < image.shape[0] for _ in [1]):
 
 
 
 
 
 
586
  image = cv2.line(image, point1, point2, color=(255, 0, 0), thickness=2)
587
  distance_text = "- **Distance: Unable to compute**"
588
- if points3d is not None and all(0 <= p[1] < points3d.shape[0] and 0 <= p[0] < points3d.shape[1] for p in [point1, point2]):
 
 
 
589
  try:
590
- distance = np.linalg.norm(points3d[point1[1], point1[0]] - points3d[point2[1], point2[0]])
 
 
591
  distance_text = f"- **Distance: {distance:.2f}m**"
592
  except Exception as e:
593
  distance_text = f"- **Distance error: {e}**"
@@ -606,7 +961,10 @@ def update_log():
606
  return "⏳ Loading and reconstructing…"
607
 
608
 
609
- def update_visualization(target_dir, frame_filter, show_cam, is_example, filter_black_bg=False, filter_white_bg=False, show_mesh=True):
 
 
 
610
  if is_example == "True":
611
  return gr.update(), "No reconstruction available. Please click Reconstruct first."
612
  if not target_dir or target_dir == "None" or not os.path.isdir(target_dir):
@@ -623,14 +981,24 @@ def update_visualization(target_dir, frame_filter, show_cam, is_example, filter_
623
  f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}_black{filter_black_bg}_white{filter_white_bg}.glb",
624
  )
625
  if not os.path.exists(glbfile):
626
- glbscene = predictions_to_glb(predictions, filter_by_frames=frame_filter, show_cam=show_cam, mask_black_bg=filter_black_bg, mask_white_bg=filter_white_bg, as_mesh=show_mesh)
 
 
 
 
 
 
 
627
  glbscene.export(file_obj=glbfile)
628
 
629
  rrd_path = predictions_to_rrd(predictions, glbfile, target_dir, frame_filter, show_cam)
630
  return rrd_path, "Visualization updated."
631
 
632
 
633
- def update_all_views_on_filter_change(target_dir, filter_black_bg, filter_white_bg, processed_data, depth_view_selector, normal_view_selector, measure_view_selector):
 
 
 
634
  if not target_dir or target_dir == "None" or not os.path.isdir(target_dir):
635
  return processed_data, None, None, None, []
636
  predictions_path = os.path.join(target_dir, "predictions.npz")
@@ -640,12 +1008,16 @@ def update_all_views_on_filter_change(target_dir, filter_black_bg, filter_white_
640
  loaded = np.load(predictions_path, allow_pickle=True)
641
  predictions = {key: loaded[key] for key in loaded.keys()}
642
  views = load_images(os.path.join(target_dir, "images"))
643
- new_processed_data = process_predictions_for_visualization(predictions, views, high_level_config, filter_black_bg, filter_white_bg)
 
 
 
644
  def safe_idx(sel):
645
  try:
646
  return int(sel.split()[1]) - 1
647
- except:
648
  return 0
 
649
  depth_vis = update_depth_view(new_processed_data, safe_idx(depth_view_selector))
650
  normal_vis = update_normal_view(new_processed_data, safe_idx(normal_view_selector))
651
  measure_img, _ = update_measure_view(new_processed_data, safe_idx(measure_view_selector))
@@ -654,7 +1026,6 @@ def update_all_views_on_filter_change(target_dir, filter_black_bg, filter_white_
654
  print(f"Filter change error: {e}")
655
  return processed_data, None, None, None, []
656
 
657
-
658
  def get_scene_info(examples_dir):
659
  import glob
660
  scenes = []
@@ -669,7 +1040,13 @@ def get_scene_info(examples_dir):
669
  image_files.extend(glob.glob(os.path.join(scene_path, ext.upper())))
670
  if image_files:
671
  image_files = sorted(image_files)
672
- scenes.append({"name": scene_folder, "path": scene_path, "thumbnail": image_files[0], "num_images": len(image_files), "image_files": image_files})
 
 
 
 
 
 
673
  return scenes
674
 
675
 
@@ -682,132 +1059,6 @@ def load_example_scene(scene_name, examples_dir="examples"):
682
  return None, target_dir, image_paths, f"Loaded '{scene_name}' β€” {selected_scene['num_images']} images. Click Reconstruct."
683
 
684
 
685
- CUSTOM_CSS = (GRADIO_CSS or "") + """
686
- /* ── Page shell ── */
687
- #app-shell {
688
- max-width: 1400px;
689
- margin: 0 auto;
690
- padding: 0 16px 40px;
691
- }
692
-
693
- /* ── Header ── */
694
- #app-header {
695
- padding: 28px 0 20px;
696
- border-bottom: 1px solid var(--border-color-primary);
697
- margin-bottom: 24px;
698
- }
699
- #app-header h1 {
700
- font-size: 2rem !important;
701
- font-weight: 700 !important;
702
- margin: 0 0 4px !important;
703
- line-height: 1.2 !important;
704
- }
705
- #app-header p {
706
- margin: 0 !important;
707
- opacity: 0.65;
708
- font-size: 0.95rem !important;
709
- }
710
-
711
- /* ── Two-panel layout ── */
712
- #left-panel { min-width: 320px; max-width: 380px; }
713
- #right-panel { flex: 1; min-width: 0; }
714
-
715
- /* ── Section labels ── */
716
- .section-label {
717
- font-size: 0.7rem !important;
718
- font-weight: 600 !important;
719
- letter-spacing: 0.08em !important;
720
- text-transform: uppercase !important;
721
- opacity: 0.5 !important;
722
- margin-bottom: 6px !important;
723
- margin-top: 16px !important;
724
- display: block !important;
725
- }
726
-
727
- /* ── Upload zone ── */
728
- #upload-zone .wrap {
729
- border-radius: 10px !important;
730
- min-height: 110px !important;
731
- }
732
-
733
- /* ── Gallery ── */
734
- #preview-gallery { border-radius: 10px; overflow: hidden; }
735
-
736
- /* ── Action buttons ── */
737
- #btn-reconstruct {
738
- width: 100% !important;
739
- font-size: 0.95rem !important;
740
- font-weight: 600 !important;
741
- padding: 12px !important;
742
- border-radius: 8px !important;
743
- }
744
-
745
- /* ── Log strip ── */
746
- #log-strip {
747
- font-size: 0.82rem !important;
748
- padding: 8px 12px !important;
749
- border-radius: 6px !important;
750
- border: 1px solid var(--border-color-primary) !important;
751
- background: var(--background-fill-secondary) !important;
752
- min-height: 36px !important;
753
- }
754
-
755
- /* ── Viewer tabs ── */
756
- #viewer-tabs .tab-nav button {
757
- font-size: 0.8rem !important;
758
- font-weight: 500 !important;
759
- padding: 6px 14px !important;
760
- }
761
- #viewer-tabs > .tabitem { padding: 0 !important; }
762
-
763
- /* ── Navigation rows inside tabs ── */
764
- .nav-row { align-items: center !important; gap: 6px !important; margin-bottom: 8px !important; }
765
- .nav-row button { min-width: 80px !important; }
766
-
767
- /* ── Options panel ── */
768
- #options-panel {
769
- border: 1px solid var(--border-color-primary);
770
- border-radius: 10px;
771
- padding: 16px;
772
- margin-top: 12px;
773
- }
774
- #options-panel .gr-markdown h3 {
775
- font-size: 0.72rem !important;
776
- font-weight: 600 !important;
777
- letter-spacing: 0.07em !important;
778
- text-transform: uppercase !important;
779
- opacity: 0.5 !important;
780
- margin: 14px 0 6px !important;
781
- }
782
- #options-panel .gr-markdown h3:first-child { margin-top: 0 !important; }
783
-
784
- /* ── Frame filter ── */
785
- #frame-filter { margin-top: 12px; }
786
-
787
- /* ── Examples section ── */
788
- #examples-section { margin-top: 36px; padding-top: 24px; border-top: 1px solid var(--border-color-primary); }
789
- #examples-section h2 { font-size: 1.1rem !important; font-weight: 600 !important; margin-bottom: 4px !important; }
790
- #examples-section .scene-caption {
791
- font-size: 0.75rem !important;
792
- text-align: center !important;
793
- opacity: 0.65 !important;
794
- margin-top: 4px !important;
795
- }
796
- .scene-thumb img { border-radius: 8px; transition: opacity .15s; }
797
- .scene-thumb img:hover { opacity: .85; }
798
-
799
- /* ── Measure note ── */
800
- .measure-note { font-size: 0.78rem !important; opacity: 0.6 !important; margin-top: 6px !important; }
801
-
802
- #col-container {
803
- margin: 0 auto;
804
- max-width: 960px;
805
- }
806
- #main-title h1 {font-size: 2.3em !important;}
807
-
808
-
809
- """
810
-
811
  with gr.Blocks() as demo:
812
 
813
  is_example = gr.Textbox(visible=False, value="None")
@@ -817,19 +1068,20 @@ with gr.Blocks() as demo:
817
  target_dir_output = gr.Textbox(visible=False, value="None")
818
 
819
  with gr.Column(elem_id="app-shell"):
820
- with gr.Column(elem_id="app-header"):
821
- gr.Markdown("# **Map-Anything-v1**", elem_id="main-title")
822
- gr.Markdown("Universal Feed-Forward Metric 3D Reconstruction (Point Cloud and Camera Poses)")
823
 
824
  with gr.Row(equal_height=False):
825
 
 
826
  with gr.Column(elem_id="left-panel", scale=0):
827
 
828
  unified_upload = gr.File(
829
  file_count="multiple",
830
  label="Upload Images/Videos",
831
  file_types=["image", "video"],
832
- height="150"
833
  )
834
 
835
  with gr.Row():
@@ -843,7 +1095,6 @@ with gr.Blocks() as demo:
843
  image_gallery = gr.Gallery(
844
  columns=2,
845
  height="150",
846
-
847
  )
848
 
849
  gr.ClearButton(
@@ -857,15 +1108,16 @@ with gr.Blocks() as demo:
857
 
858
  with gr.Accordion("Options", open=False):
859
  gr.Markdown("### Point Cloud")
860
- show_cam = gr.Checkbox(label="Show cameras", value=True)
861
- show_mesh = gr.Checkbox(label="Show mesh", value=True)
862
- filter_black_bg = gr.Checkbox(label="Filter black background", value=False)
863
- filter_white_bg = gr.Checkbox(label="Filter white background", value=False)
864
  gr.Markdown("### Reconstruction (next run)")
865
  apply_mask_checkbox = gr.Checkbox(
866
- label="Apply ambiguous-depth mask & edges", value=True
867
  )
868
 
 
869
  with gr.Column(elem_id="right-panel", scale=1):
870
 
871
  log_output = gr.Markdown(
@@ -878,7 +1130,7 @@ with gr.Blocks() as demo:
878
  with gr.Tab("3D View"):
879
  reconstruction_output = Rerun(
880
  label="Rerun 3D Viewer",
881
- height=680,
882
  )
883
 
884
  with gr.Tab("Depth"):
@@ -934,7 +1186,7 @@ with gr.Blocks() as demo:
934
  choices=["All"], value="All", label="Filter by Frame",
935
  show_label=True,
936
  )
937
-
938
  with gr.Column(elem_id="examples-section"):
939
  gr.Markdown("## Example Scenes")
940
  gr.Markdown("Click a thumbnail to load the scene, then press **Reconstruct**.")
@@ -967,10 +1219,11 @@ with gr.Blocks() as demo:
967
  with gr.Column(scale=1, min_width=140):
968
  pass
969
 
 
970
  submit_btn.click(
971
- fn=clear_fields, inputs=[], outputs=[reconstruction_output]
972
  ).then(
973
- fn=update_log, inputs=[], outputs=[log_output]
974
  ).then(
975
  fn=gradio_demo,
976
  inputs=[target_dir_output, frame_filter, show_cam, filter_black_bg, filter_white_bg, apply_mask_checkbox, show_mesh],
@@ -997,6 +1250,7 @@ with gr.Blocks() as demo:
997
  [target_dir_output, filter_black_bg, filter_white_bg, processed_data_state, depth_view_selector, normal_view_selector, measure_view_selector],
998
  [processed_data_state, depth_map, normal_map, measure_image, measure_points_state],
999
  )
 
1000
  filter_white_bg.change(
1001
  update_visualization,
1002
  [target_dir_output, frame_filter, show_cam, is_example, filter_black_bg, filter_white_bg, show_mesh],
@@ -1017,14 +1271,20 @@ with gr.Blocks() as demo:
1017
  if not files:
1018
  return gr.update(visible=False)
1019
  video_exts = [".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp"]
1020
- has_video = any(os.path.splitext(str(f["name"] if isinstance(f, dict) else f))[1].lower() in video_exts for f in files)
 
 
 
1021
  return gr.update(visible=has_video)
1022
 
1023
  def resample_video_with_new_interval(files, new_interval, current_target_dir):
1024
  if not files:
1025
  return current_target_dir, None, "No files to resample.", gr.update(visible=False)
1026
  video_exts = [".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp"]
1027
- if not any(os.path.splitext(str(f["name"] if isinstance(f, dict) else f))[1].lower() in video_exts for f in files):
 
 
 
1028
  return current_target_dir, None, "No videos found.", gr.update(visible=False)
1029
  if current_target_dir and current_target_dir != "None" and os.path.exists(current_target_dir):
1030
  shutil.rmtree(current_target_dir)
@@ -1052,28 +1312,34 @@ with gr.Blocks() as demo:
1052
 
1053
  prev_depth_btn.click(
1054
  fn=lambda pd, sel: navigate_depth_view(pd, sel, -1),
1055
- inputs=[processed_data_state, depth_view_selector], outputs=[depth_view_selector, depth_map],
 
1056
  )
1057
  next_depth_btn.click(
1058
  fn=lambda pd, sel: navigate_depth_view(pd, sel, 1),
1059
- inputs=[processed_data_state, depth_view_selector], outputs=[depth_view_selector, depth_map],
 
1060
  )
1061
  depth_view_selector.change(
1062
  fn=lambda pd, sel: update_depth_view(pd, int(sel.split()[1]) - 1) if sel else None,
1063
- inputs=[processed_data_state, depth_view_selector], outputs=[depth_map],
 
1064
  )
1065
 
1066
  prev_normal_btn.click(
1067
  fn=lambda pd, sel: navigate_normal_view(pd, sel, -1),
1068
- inputs=[processed_data_state, normal_view_selector], outputs=[normal_view_selector, normal_map],
 
1069
  )
1070
  next_normal_btn.click(
1071
  fn=lambda pd, sel: navigate_normal_view(pd, sel, 1),
1072
- inputs=[processed_data_state, normal_view_selector], outputs=[normal_view_selector, normal_map],
 
1073
  )
1074
  normal_view_selector.change(
1075
  fn=lambda pd, sel: update_normal_view(pd, int(sel.split()[1]) - 1) if sel else None,
1076
- inputs=[processed_data_state, normal_view_selector], outputs=[normal_map],
 
1077
  )
1078
 
1079
  prev_measure_btn.click(
@@ -1092,4 +1358,4 @@ with gr.Blocks() as demo:
1092
  outputs=[measure_image, measure_points_state],
1093
  )
1094
 
1095
- demo.queue(max_size=50).launch(theme=orange_red_theme, css=CUSTOM_CSS, show_error=True, share=True, ssr_mode=False)
 
111
  block_label_background_fill="*primary_200",
112
  )
113
 
114
+
115
  orange_red_theme = OrangeRedTheme()
116
 
117
+ SVG_CUBE = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/></svg>'
118
+
119
+ SVG_CHIP = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Z"/></svg>'
120
+
121
+ def html_header():
122
+ return f"""
123
+ <div class="app-header">
124
+ <div class="header-content">
125
+ <div class="header-icon-wrap">{SVG_CUBE}</div>
126
+ <div class="header-text">
127
+ <h1>Map-Anything &mdash; v1</h1>
128
+ <div class="header-meta">
129
+ <span class="meta-badge">{SVG_CHIP} facebook/map-anything-v1</span>
130
+ <span class="meta-sep"></span>
131
+ <span class="meta-cap">3D Reconstruction</span>
132
+ <span class="meta-sep"></span>
133
+ <span class="meta-cap">Depth Estimation</span>
134
+ <span class="meta-sep"></span>
135
+ <span class="meta-cap">Normal Maps</span>
136
+ <span class="meta-sep"></span>
137
+ <span class="meta-cap">Measurements</span>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ """
143
+
144
+
145
  high_level_config = {
146
  "path": "configs/train.yaml",
147
  "hf_model_name": "facebook/map-anything-v1",
 
165
  TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
166
  os.makedirs(TMP_DIR, exist_ok=True)
167
 
168
+ CUSTOM_CSS = (GRADIO_CSS or "") + r"""
169
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
170
+
171
+ body, .gradio-container { font-family: 'Outfit', sans-serif !important; }
172
+ footer { display: none !important; }
173
+
174
+ /* ── App Header ── */
175
+ .app-header {
176
+ background: linear-gradient(135deg, #4A1800 0%, #802200 30%, #CC3700 70%, #FF4500 100%);
177
+ border-radius: 16px;
178
+ padding: 32px 40px;
179
+ margin-bottom: 24px;
180
+ position: relative;
181
+ overflow: hidden;
182
+ box-shadow: 0 8px 32px rgba(74, 24, 0, 0.25);
183
+ }
184
+ .app-header::before {
185
+ content: '';
186
+ position: absolute;
187
+ top: -50%;
188
+ right: -20%;
189
+ width: 400px;
190
+ height: 400px;
191
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.06) 0%, transparent 70%);
192
+ border-radius: 50%;
193
+ }
194
+ .app-header::after {
195
+ content: '';
196
+ position: absolute;
197
+ bottom: -30%;
198
+ left: -10%;
199
+ width: 300px;
200
+ height: 300px;
201
+ background: radial-gradient(circle, rgba(255, 69, 0, 0.15) 0%, transparent 70%);
202
+ border-radius: 50%;
203
+ }
204
+ .header-content {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 24px;
208
+ position: relative;
209
+ z-index: 1;
210
+ }
211
+ .header-icon-wrap {
212
+ width: 64px;
213
+ height: 64px;
214
+ background: rgba(255, 255, 255, 0.12);
215
+ border-radius: 16px;
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: center;
219
+ flex-shrink: 0;
220
+ backdrop-filter: blur(8px);
221
+ border: 1px solid rgba(255, 255, 255, 0.15);
222
+ }
223
+ .header-icon-wrap svg {
224
+ width: 36px;
225
+ height: 36px;
226
+ color: rgba(255, 255, 255, 0.9);
227
+ }
228
+ .header-text h1 {
229
+ font-family: 'Outfit', sans-serif;
230
+ font-size: 2rem;
231
+ font-weight: 700;
232
+ color: #fff;
233
+ margin: 0 0 8px 0;
234
+ letter-spacing: -0.02em;
235
+ line-height: 1.2;
236
+ }
237
+ .header-meta {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 12px;
241
+ flex-wrap: wrap;
242
+ }
243
+ .meta-badge {
244
+ display: inline-flex;
245
+ align-items: center;
246
+ gap: 6px;
247
+ background: rgba(255, 255, 255, 0.12);
248
+ color: rgba(255, 255, 255, 0.9);
249
+ padding: 4px 12px;
250
+ border-radius: 20px;
251
+ font-family: 'IBM Plex Mono', monospace;
252
+ font-size: 0.8rem;
253
+ font-weight: 500;
254
+ border: 1px solid rgba(255, 255, 255, 0.1);
255
+ backdrop-filter: blur(4px);
256
+ }
257
+ .meta-badge svg {
258
+ width: 14px;
259
+ height: 14px;
260
+ }
261
+ .meta-sep {
262
+ width: 4px;
263
+ height: 4px;
264
+ background: rgba(255, 255, 255, 0.35);
265
+ border-radius: 50%;
266
+ flex-shrink: 0;
267
+ }
268
+ .meta-cap {
269
+ color: rgba(255, 255, 255, 0.65);
270
+ font-size: 0.85rem;
271
+ font-weight: 400;
272
+ }
273
+
274
+ /* ── Page shell ── */
275
+ #app-shell {
276
+ max-width: 1400px;
277
+ margin: 0 auto;
278
+ padding: 0 16px 40px;
279
+ }
280
+
281
+ /* ── Two-panel layout ── */
282
+ #left-panel { min-width: 320px; max-width: 380px; }
283
+ #right-panel { flex: 1; min-width: 0; }
284
+
285
+ /* ── Section labels ── */
286
+ .section-label {
287
+ font-size: 0.7rem !important;
288
+ font-weight: 600 !important;
289
+ letter-spacing: 0.08em !important;
290
+ text-transform: uppercase !important;
291
+ opacity: 0.5 !important;
292
+ margin-bottom: 6px !important;
293
+ margin-top: 16px !important;
294
+ display: block !important;
295
+ }
296
+
297
+ /* ── Upload zone ── */
298
+ #upload-zone .wrap {
299
+ border-radius: 10px !important;
300
+ min-height: 110px !important;
301
+ }
302
+
303
+ /* ── Gallery ── */
304
+ #preview-gallery { border-radius: 10px; overflow: hidden; }
305
+
306
+ /* ── Action buttons ── */
307
+ #btn-reconstruct {
308
+ width: 100% !important;
309
+ font-size: 0.95rem !important;
310
+ font-weight: 600 !important;
311
+ padding: 12px !important;
312
+ border-radius: 8px !important;
313
+ }
314
+
315
+ /* ── Buttons ── */
316
+ .primary {
317
+ border-radius: 10px !important;
318
+ font-weight: 600 !important;
319
+ letter-spacing: 0.02em !important;
320
+ transition: all 0.25s ease !important;
321
+ font-family: 'Outfit', sans-serif !important;
322
+ }
323
+ .primary:hover {
324
+ transform: translateY(-2px) !important;
325
+ box-shadow: 0 6px 20px rgba(255, 69, 0, 0.3) !important;
326
+ }
327
+ .primary:active { transform: translateY(0) !important; }
328
+
329
+ /* ── Log strip ── */
330
+ #log-strip {
331
+ font-size: 0.82rem !important;
332
+ padding: 8px 12px !important;
333
+ border-radius: 6px !important;
334
+ border: 1px solid var(--border-color-primary) !important;
335
+ background: var(--background-fill-secondary) !important;
336
+ min-height: 36px !important;
337
+ }
338
+
339
+ /* ── Viewer tabs ── */
340
+ #viewer-tabs .tab-nav button {
341
+ font-size: 0.8rem !important;
342
+ font-weight: 500 !important;
343
+ padding: 6px 14px !important;
344
+ }
345
+ #viewer-tabs > .tabitem { padding: 0 !important; }
346
+
347
+ /* ── Tab transitions ── */
348
+ .gradio-tabitem { animation: tabFadeIn 0.35s ease-out; }
349
+ @keyframes tabFadeIn {
350
+ from { opacity: 0; transform: translateY(6px); }
351
+ to { opacity: 1; transform: translateY(0); }
352
+ }
353
+
354
+ /* ── Navigation rows inside tabs ── */
355
+ .nav-row { align-items: center !important; gap: 6px !important; margin-bottom: 8px !important; }
356
+ .nav-row button { min-width: 80px !important; }
357
+
358
+ /* ── Options panel ── */
359
+ #options-panel {
360
+ border: 1px solid var(--border-color-primary);
361
+ border-radius: 10px;
362
+ padding: 16px;
363
+ margin-top: 12px;
364
+ }
365
+ #options-panel .gr-markdown h3 {
366
+ font-size: 0.72rem !important;
367
+ font-weight: 600 !important;
368
+ letter-spacing: 0.07em !important;
369
+ text-transform: uppercase !important;
370
+ opacity: 0.5 !important;
371
+ margin: 14px 0 6px !important;
372
+ }
373
+ #options-panel .gr-markdown h3:first-child { margin-top: 0 !important; }
374
+
375
+ /* ── Frame filter ── */
376
+ #frame-filter { margin-top: 12px; }
377
+
378
+ /* ── Examples section ── */
379
+ #examples-section {
380
+ margin-top: 36px;
381
+ padding-top: 24px;
382
+ border-top: 1px solid var(--border-color-primary);
383
+ }
384
+ #examples-section h2 {
385
+ font-size: 1.1rem !important;
386
+ font-weight: 600 !important;
387
+ margin-bottom: 4px !important;
388
+ }
389
+ #examples-section .scene-caption {
390
+ font-size: 0.75rem !important;
391
+ text-align: center !important;
392
+ opacity: 0.65 !important;
393
+ margin-top: 4px !important;
394
+ }
395
+ .scene-thumb img { border-radius: 8px; transition: opacity .15s; }
396
+ .scene-thumb img:hover { opacity: .85; }
397
+
398
+ /* ── Measure note ── */
399
+ .measure-note {
400
+ font-size: 0.78rem !important;
401
+ opacity: 0.6 !important;
402
+ margin-top: 6px !important;
403
+ }
404
+
405
+ #col-container {
406
+ margin: 0 auto;
407
+ max-width: 960px;
408
+ }
409
+
410
+ /* ── Accordion ── */
411
+ .gradio-accordion {
412
+ border-radius: 10px !important;
413
+ border: 1px solid rgba(255, 69, 0, 0.15) !important;
414
+ }
415
+ .gradio-accordion > .label-wrap { border-radius: 10px !important; }
416
+
417
+ /* ── Labels ── */
418
+ label {
419
+ font-weight: 600 !important;
420
+ font-family: 'Outfit', sans-serif !important;
421
+ }
422
+
423
+ /* ── Slider ── */
424
+ .gradio-slider input[type="range"] { accent-color: #FF4500 !important; }
425
+
426
+ /* ── Scrollbar ── */
427
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
428
+ ::-webkit-scrollbar-track { background: rgba(255, 69, 0, 0.04); border-radius: 4px; }
429
+ ::-webkit-scrollbar-thumb {
430
+ background: linear-gradient(135deg, #FF4500, #CC3700);
431
+ border-radius: 4px;
432
+ }
433
+ ::-webkit-scrollbar-thumb:hover {
434
+ background: linear-gradient(135deg, #CC3700, #992900);
435
+ }
436
+
437
+ /* ── Responsive ── */
438
+ @media (max-width: 768px) {
439
+ .app-header { padding: 20px 24px; }
440
+ .header-text h1 { font-size: 1.5rem; }
441
+ .header-content {
442
+ flex-direction: column;
443
+ align-items: flex-start;
444
+ gap: 16px;
445
+ }
446
+ .header-meta { gap: 8px; }
447
+ }
448
+ """
449
+
450
+
451
  def predictions_to_rrd(predictions, glbfile, target_dir, frame_filter="All", show_cam=True):
452
  run_id = str(uuid.uuid4())
453
  timestamp = datetime.now().strftime("%Y-%m-%dT%H%M%S")
 
604
  torch.cuda.empty_cache()
605
  return predictions, processed_data
606
 
 
607
  def update_view_selectors(processed_data):
608
  choices = [f"View {i + 1}" for i in range(len(processed_data))] if processed_data else ["View 1"]
609
  return (
 
648
  overlay_color = np.array([255, 220, 220], dtype=np.uint8)
649
  alpha = 0.5
650
  for c in range(3):
651
+ image[:, :, c] = np.where(
652
+ invalid_mask,
653
+ (1 - alpha) * image[:, :, c] + alpha * overlay_color[c],
654
+ image[:, :, c],
655
+ ).astype(np.uint8)
656
  return image, []
657
 
658
 
 
661
  return "View 1", None
662
  try:
663
  current_view = int(current_selector_value.split()[1]) - 1
664
+ except Exception:
665
  current_view = 0
666
  new_view = (current_view + direction) % len(processed_data)
667
  return f"View {new_view + 1}", update_depth_view(processed_data, new_view)
 
672
  return "View 1", None
673
  try:
674
  current_view = int(current_selector_value.split()[1]) - 1
675
+ except Exception:
676
  current_view = 0
677
  new_view = (current_view + direction) % len(processed_data)
678
  return f"View {new_view + 1}", update_normal_view(processed_data, new_view)
 
683
  return "View 1", None, []
684
  try:
685
  current_view = int(current_selector_value.split()[1]) - 1
686
+ except Exception:
687
  current_view = 0
688
  new_view = (current_view + direction) % len(processed_data)
689
  measure_image, measure_points = update_measure_view(processed_data, new_view)
 
693
  def populate_visualization_tabs(processed_data):
694
  if not processed_data:
695
  return None, None, None, []
696
+ return (
697
+ update_depth_view(processed_data, 0),
698
+ update_normal_view(processed_data, 0),
699
+ update_measure_view(processed_data, 0)[0],
700
+ [],
701
+ )
702
+
703
 
704
  def handle_uploads(unified_upload, s_time_interval=1.0):
705
  start_time = time.time()
 
790
  target_dir,
791
  f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}_black{filter_black_bg}_white{filter_white_bg}.glb",
792
  )
793
+ glbscene = predictions_to_glb(
794
+ predictions,
795
+ filter_by_frames=frame_filter,
796
+ show_cam=show_cam,
797
+ mask_black_bg=filter_black_bg,
798
+ mask_white_bg=filter_white_bg,
799
+ as_mesh=show_mesh,
800
+ )
801
  glbscene.export(file_obj=glbfile)
802
 
803
  rrd_path = predictions_to_rrd(predictions, glbfile, target_dir, frame_filter, show_cam)
 
857
  mask = mask & (view_colors.sum(axis=2) >= 16)
858
  if filter_white_bg:
859
  view_colors = image[0] * 255 if image[0].max() <= 1.0 else image[0]
860
+ mask = mask & ~(
861
+ (view_colors[:, :, 0] > 240)
862
+ & (view_colors[:, :, 1] > 240)
863
+ & (view_colors[:, :, 2] > 240)
864
+ )
865
  normals, _ = points_to_normals(pred_pts3d, mask=mask)
866
  processed_data[view_idx] = {
867
  "image": image[0],
 
879
  return None, [], "No data available"
880
  try:
881
  current_view_index = int(current_view_selector.split()[1]) - 1
882
+ except Exception:
883
  current_view_index = 0
884
  current_view_index = max(0, min(current_view_index, len(processed_data) - 1))
885
  current_view = processed_data[list(processed_data.keys())[current_view_index]]
 
887
  return None, [], "No view data available"
888
 
889
  point2d = event.index[0], event.index[1]
890
+ if (
891
+ current_view["mask"] is not None
892
+ and 0 <= point2d[1] < current_view["mask"].shape[0]
893
+ and 0 <= point2d[0] < current_view["mask"].shape[1]
894
+ ):
895
  if not current_view["mask"][point2d[1], point2d[0]]:
896
  masked_image, _ = update_measure_view(processed_data, current_view_index)
897
  return masked_image, measure_points, '<span style="color: red; font-weight: bold;">Cannot measure on masked areas</span>'
 
911
 
912
  depth_text = ""
913
  for i, p in enumerate(measure_points):
914
+ if (
915
+ current_view["depth"] is not None
916
+ and 0 <= p[1] < current_view["depth"].shape[0]
917
+ and 0 <= p[0] < current_view["depth"].shape[1]
918
+ ):
919
  depth_text += f"- **P{i + 1} depth: {current_view['depth'][p[1], p[0]]:.2f}m**\n"
920
+ elif (
921
+ points3d is not None
922
+ and 0 <= p[1] < points3d.shape[0]
923
+ and 0 <= p[0] < points3d.shape[1]
924
+ ):
925
  depth_text += f"- **P{i + 1} Z-coord: {points3d[p[1], p[0], 2]:.2f}m**\n"
926
 
927
  if len(measure_points) == 2:
928
  point1, point2 = measure_points
929
+ if all(
930
+ 0 <= point1[0] < image.shape[1]
931
+ and 0 <= point1[1] < image.shape[0]
932
+ and 0 <= point2[0] < image.shape[1]
933
+ and 0 <= point2[1] < image.shape[0]
934
+ for _ in [1]
935
+ ):
936
  image = cv2.line(image, point1, point2, color=(255, 0, 0), thickness=2)
937
  distance_text = "- **Distance: Unable to compute**"
938
+ if points3d is not None and all(
939
+ 0 <= p[1] < points3d.shape[0] and 0 <= p[0] < points3d.shape[1]
940
+ for p in [point1, point2]
941
+ ):
942
  try:
943
+ distance = np.linalg.norm(
944
+ points3d[point1[1], point1[0]] - points3d[point2[1], point2[0]]
945
+ )
946
  distance_text = f"- **Distance: {distance:.2f}m**"
947
  except Exception as e:
948
  distance_text = f"- **Distance error: {e}**"
 
961
  return "⏳ Loading and reconstructing…"
962
 
963
 
964
+ def update_visualization(
965
+ target_dir, frame_filter, show_cam, is_example,
966
+ filter_black_bg=False, filter_white_bg=False, show_mesh=True,
967
+ ):
968
  if is_example == "True":
969
  return gr.update(), "No reconstruction available. Please click Reconstruct first."
970
  if not target_dir or target_dir == "None" or not os.path.isdir(target_dir):
 
981
  f"glbscene_{frame_filter.replace('.', '_').replace(':', '').replace(' ', '_')}_cam{show_cam}_mesh{show_mesh}_black{filter_black_bg}_white{filter_white_bg}.glb",
982
  )
983
  if not os.path.exists(glbfile):
984
+ glbscene = predictions_to_glb(
985
+ predictions,
986
+ filter_by_frames=frame_filter,
987
+ show_cam=show_cam,
988
+ mask_black_bg=filter_black_bg,
989
+ mask_white_bg=filter_white_bg,
990
+ as_mesh=show_mesh,
991
+ )
992
  glbscene.export(file_obj=glbfile)
993
 
994
  rrd_path = predictions_to_rrd(predictions, glbfile, target_dir, frame_filter, show_cam)
995
  return rrd_path, "Visualization updated."
996
 
997
 
998
+ def update_all_views_on_filter_change(
999
+ target_dir, filter_black_bg, filter_white_bg, processed_data,
1000
+ depth_view_selector, normal_view_selector, measure_view_selector,
1001
+ ):
1002
  if not target_dir or target_dir == "None" or not os.path.isdir(target_dir):
1003
  return processed_data, None, None, None, []
1004
  predictions_path = os.path.join(target_dir, "predictions.npz")
 
1008
  loaded = np.load(predictions_path, allow_pickle=True)
1009
  predictions = {key: loaded[key] for key in loaded.keys()}
1010
  views = load_images(os.path.join(target_dir, "images"))
1011
+ new_processed_data = process_predictions_for_visualization(
1012
+ predictions, views, high_level_config, filter_black_bg, filter_white_bg,
1013
+ )
1014
+
1015
  def safe_idx(sel):
1016
  try:
1017
  return int(sel.split()[1]) - 1
1018
+ except Exception:
1019
  return 0
1020
+
1021
  depth_vis = update_depth_view(new_processed_data, safe_idx(depth_view_selector))
1022
  normal_vis = update_normal_view(new_processed_data, safe_idx(normal_view_selector))
1023
  measure_img, _ = update_measure_view(new_processed_data, safe_idx(measure_view_selector))
 
1026
  print(f"Filter change error: {e}")
1027
  return processed_data, None, None, None, []
1028
 
 
1029
  def get_scene_info(examples_dir):
1030
  import glob
1031
  scenes = []
 
1040
  image_files.extend(glob.glob(os.path.join(scene_path, ext.upper())))
1041
  if image_files:
1042
  image_files = sorted(image_files)
1043
+ scenes.append({
1044
+ "name": scene_folder,
1045
+ "path": scene_path,
1046
+ "thumbnail": image_files[0],
1047
+ "num_images": len(image_files),
1048
+ "image_files": image_files,
1049
+ })
1050
  return scenes
1051
 
1052
 
 
1059
  return None, target_dir, image_paths, f"Loaded '{scene_name}' β€” {selected_scene['num_images']} images. Click Reconstruct."
1060
 
1061
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1062
  with gr.Blocks() as demo:
1063
 
1064
  is_example = gr.Textbox(visible=False, value="None")
 
1068
  target_dir_output = gr.Textbox(visible=False, value="None")
1069
 
1070
  with gr.Column(elem_id="app-shell"):
1071
+
1072
+ # ── New styled header ──
1073
+ gr.HTML(html_header())
1074
 
1075
  with gr.Row(equal_height=False):
1076
 
1077
+ # ── Left Panel ──
1078
  with gr.Column(elem_id="left-panel", scale=0):
1079
 
1080
  unified_upload = gr.File(
1081
  file_count="multiple",
1082
  label="Upload Images/Videos",
1083
  file_types=["image", "video"],
1084
+ height="150",
1085
  )
1086
 
1087
  with gr.Row():
 
1095
  image_gallery = gr.Gallery(
1096
  columns=2,
1097
  height="150",
 
1098
  )
1099
 
1100
  gr.ClearButton(
 
1108
 
1109
  with gr.Accordion("Options", open=False):
1110
  gr.Markdown("### Point Cloud")
1111
+ show_cam = gr.Checkbox(label="Show cameras", value=True)
1112
+ show_mesh = gr.Checkbox(label="Show mesh", value=True)
1113
+ filter_black_bg = gr.Checkbox(label="Filter black background", value=False)
1114
+ filter_white_bg = gr.Checkbox(label="Filter white background", value=False)
1115
  gr.Markdown("### Reconstruction (next run)")
1116
  apply_mask_checkbox = gr.Checkbox(
1117
+ label="Apply ambiguous-depth mask & edges", value=True,
1118
  )
1119
 
1120
+ # ── Right Panel ──
1121
  with gr.Column(elem_id="right-panel", scale=1):
1122
 
1123
  log_output = gr.Markdown(
 
1130
  with gr.Tab("3D View"):
1131
  reconstruction_output = Rerun(
1132
  label="Rerun 3D Viewer",
1133
+ height=675,
1134
  )
1135
 
1136
  with gr.Tab("Depth"):
 
1186
  choices=["All"], value="All", label="Filter by Frame",
1187
  show_label=True,
1188
  )
1189
+
1190
  with gr.Column(elem_id="examples-section"):
1191
  gr.Markdown("## Example Scenes")
1192
  gr.Markdown("Click a thumbnail to load the scene, then press **Reconstruct**.")
 
1219
  with gr.Column(scale=1, min_width=140):
1220
  pass
1221
 
1222
+
1223
  submit_btn.click(
1224
+ fn=clear_fields, inputs=[], outputs=[reconstruction_output],
1225
  ).then(
1226
+ fn=update_log, inputs=[], outputs=[log_output],
1227
  ).then(
1228
  fn=gradio_demo,
1229
  inputs=[target_dir_output, frame_filter, show_cam, filter_black_bg, filter_white_bg, apply_mask_checkbox, show_mesh],
 
1250
  [target_dir_output, filter_black_bg, filter_white_bg, processed_data_state, depth_view_selector, normal_view_selector, measure_view_selector],
1251
  [processed_data_state, depth_map, normal_map, measure_image, measure_points_state],
1252
  )
1253
+
1254
  filter_white_bg.change(
1255
  update_visualization,
1256
  [target_dir_output, frame_filter, show_cam, is_example, filter_black_bg, filter_white_bg, show_mesh],
 
1271
  if not files:
1272
  return gr.update(visible=False)
1273
  video_exts = [".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp"]
1274
+ has_video = any(
1275
+ os.path.splitext(str(f["name"] if isinstance(f, dict) else f))[1].lower() in video_exts
1276
+ for f in files
1277
+ )
1278
  return gr.update(visible=has_video)
1279
 
1280
  def resample_video_with_new_interval(files, new_interval, current_target_dir):
1281
  if not files:
1282
  return current_target_dir, None, "No files to resample.", gr.update(visible=False)
1283
  video_exts = [".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v", ".3gp"]
1284
+ if not any(
1285
+ os.path.splitext(str(f["name"] if isinstance(f, dict) else f))[1].lower() in video_exts
1286
+ for f in files
1287
+ ):
1288
  return current_target_dir, None, "No videos found.", gr.update(visible=False)
1289
  if current_target_dir and current_target_dir != "None" and os.path.exists(current_target_dir):
1290
  shutil.rmtree(current_target_dir)
 
1312
 
1313
  prev_depth_btn.click(
1314
  fn=lambda pd, sel: navigate_depth_view(pd, sel, -1),
1315
+ inputs=[processed_data_state, depth_view_selector],
1316
+ outputs=[depth_view_selector, depth_map],
1317
  )
1318
  next_depth_btn.click(
1319
  fn=lambda pd, sel: navigate_depth_view(pd, sel, 1),
1320
+ inputs=[processed_data_state, depth_view_selector],
1321
+ outputs=[depth_view_selector, depth_map],
1322
  )
1323
  depth_view_selector.change(
1324
  fn=lambda pd, sel: update_depth_view(pd, int(sel.split()[1]) - 1) if sel else None,
1325
+ inputs=[processed_data_state, depth_view_selector],
1326
+ outputs=[depth_map],
1327
  )
1328
 
1329
  prev_normal_btn.click(
1330
  fn=lambda pd, sel: navigate_normal_view(pd, sel, -1),
1331
+ inputs=[processed_data_state, normal_view_selector],
1332
+ outputs=[normal_view_selector, normal_map],
1333
  )
1334
  next_normal_btn.click(
1335
  fn=lambda pd, sel: navigate_normal_view(pd, sel, 1),
1336
+ inputs=[processed_data_state, normal_view_selector],
1337
+ outputs=[normal_view_selector, normal_map],
1338
  )
1339
  normal_view_selector.change(
1340
  fn=lambda pd, sel: update_normal_view(pd, int(sel.split()[1]) - 1) if sel else None,
1341
+ inputs=[processed_data_state, normal_view_selector],
1342
+ outputs=[normal_map],
1343
  )
1344
 
1345
  prev_measure_btn.click(
 
1358
  outputs=[measure_image, measure_points_state],
1359
  )
1360
 
1361
+ demo.queue(max_size=50).launch(css=CUSTOM_CSS, theme=orange_red_theme, show_error=True, share=True, ssr_mode=False)