Zhaoting123 commited on
Commit
a54b7c3
·
verified ·
1 Parent(s): ccaa72d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -18
app.py CHANGED
@@ -68,6 +68,7 @@ DATASET_PRESETS = {
68
  DEFAULT_PRESET = "Robosuite Square Correction"
69
  REPO_TYPE = "dataset"
70
  DEFAULT_CHUNK_LEN = 16
 
71
  PREFERRED_IMAGE_KEYS = [
72
  "image1",
73
  "image2",
@@ -797,7 +798,58 @@ def get_cached_video_status_frame(repo_id, filename, traj_id, timestep, valid_wi
797
 
798
  return np.asarray(img)
799
 
800
- def build_current_trajectory_video(preset_name, custom_repo_id, custom_filename, traj_id, image_keys, display_scale, reverse_channels, fps, valid_window_len):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
  if imageio is None:
802
  return None, "Video export requires imageio and imageio-ffmpeg in requirements.txt."
803
 
@@ -817,16 +869,34 @@ def build_current_trajectory_video(preset_name, custom_repo_id, custom_filename,
817
  image_keys = [image_keys]
818
  image_keys_tuple = tuple(image_keys)
819
 
 
 
 
 
 
820
  safe_repo = re.sub(r"[^A-Za-z0-9_.-]+", "_", repo_id)
821
  safe_file = re.sub(r"[^A-Za-z0-9_.-]+", "_", filename)[-80:]
822
  out_path = os.path.join(
823
  tempfile.gettempdir(),
824
- "trajectory_{}_{}_traj{:04d}_fps{}.mp4".format(safe_repo, safe_file, traj_id, int(fps)),
 
 
 
 
 
 
 
 
 
 
825
  )
826
 
827
- writer = imageio.get_writer(out_path, fps=float(fps), codec="libx264", quality=8)
 
828
  try:
829
- for t in range(len(traj)):
 
 
830
  gallery_items, _warnings = get_cached_gallery_items(
831
  repo_id,
832
  filename,
@@ -837,26 +907,22 @@ def build_current_trajectory_video(preset_name, custom_repo_id, custom_filename,
837
  bool(reverse_channels),
838
  )
839
  label = "trajectory {} | frame {}/{}".format(traj_id, t, len(traj) - 1)
840
- status_plot = get_cached_video_status_frame(
841
- repo_id,
842
- filename,
843
- traj_id,
844
- t,
845
- int(valid_window_len),
846
- )
847
  frame = _compose_video_frame(gallery_items, label, status_plot=status_plot)
848
  writer.append_data(frame)
 
849
  finally:
850
  writer.close()
851
 
852
- status = "Built trajectory video with fast moving status plot"
 
853
  status += "\nTrajectory: {}".format(traj_id)
854
- status += "\nFrames: {} | FPS: {}".format(len(traj), fps)
 
855
  status += "\nValid-window length: {}".format(int(valid_window_len))
856
- status += "\nSpeedup: static plot rendered once; cursor drawn per frame"
857
  return out_path, status
858
 
859
-
860
  def get_available_image_keys(repo_id, filename, traj_id):
861
  n_traj = get_num_trajectories(repo_id, filename)
862
  if n_traj == 0:
@@ -1047,7 +1113,7 @@ def build_app():
1047
  with gr.Row():
1048
  image_keys = gr.CheckboxGroup(choices=first_keys, value=first_keys[:2], label="Image keys")
1049
  chunk_len = gr.Slider(minimum=1, maximum=64, value=DEFAULT_CHUNK_LEN, step=1, label="Valid-window length")
1050
- display_scale = gr.Slider(minimum=1, maximum=10, value=3, step=1, label="Image display scale")
1051
  reverse_channels = gr.Checkbox(value=get_default_reverse_channels(DEFAULT_PRESET), label="Reverse channels BGR↔RGB")
1052
 
1053
  with gr.Row():
@@ -1055,6 +1121,7 @@ def build_app():
1055
  preload_btn = gr.Button("Preload current trajectory")
1056
  video_btn = gr.Button("Build trajectory video")
1057
  video_fps = gr.Slider(minimum=1, maximum=30, value=10, step=1, label="Video FPS")
 
1058
 
1059
  preload_status = gr.Textbox(label="Preload / video status", lines=4, value="Not preloaded yet.", interactive=False)
1060
 
@@ -1121,7 +1188,7 @@ def build_app():
1121
  outputs=[gallery, status_plot, info],
1122
  )
1123
 
1124
- for widget in [image_keys, chunk_len, display_scale, reverse_channels]:
1125
  widget.change(
1126
  fn=render_frame,
1127
  inputs=[preset, custom_repo_id, custom_filename, traj_slider, timestep_slider, image_keys, chunk_len, display_scale, reverse_channels],
@@ -1142,7 +1209,7 @@ def build_app():
1142
 
1143
  video_btn.click(
1144
  fn=build_current_trajectory_video,
1145
- inputs=[preset, custom_repo_id, custom_filename, traj_slider, image_keys, display_scale, reverse_channels, video_fps, chunk_len],
1146
  outputs=[trajectory_video, preload_status],
1147
  )
1148
 
 
68
  DEFAULT_PRESET = "Robosuite Square Correction"
69
  REPO_TYPE = "dataset"
70
  DEFAULT_CHUNK_LEN = 16
71
+ DEFAULT_DISPLAY_SCALE = 1
72
  PREFERRED_IMAGE_KEYS = [
73
  "image1",
74
  "image2",
 
798
 
799
  return np.asarray(img)
800
 
801
+ def _draw_status_cursor_on_base(base, bounds, total_steps, timestep):
802
+ """Fast video status frame: copy one static Matplotlib image and draw cursor.
803
+
804
+ This avoids calling the lru-cached per-timestep status frame function during
805
+ video export. For long trajectories, caching thousands of status images can
806
+ consume a lot of memory and still requires PIL conversion for every frame.
807
+ """
808
+ if base is None:
809
+ return None
810
+
811
+ total_steps = int(max(total_steps, 1))
812
+ timestep = int(np.clip(int(timestep), 0, total_steps - 1))
813
+ x0, y0, x1, y1 = [int(v) for v in bounds]
814
+ denom = max(total_steps - 1, 1)
815
+ x = int(round(x0 + (x1 - x0) * float(timestep) / float(denom)))
816
+
817
+ img = np.asarray(base, dtype=np.uint8).copy()
818
+
819
+ # Draw the vertical cursor directly with NumPy. This is much cheaper than
820
+ # creating a Matplotlib plot for every frame.
821
+ x_left = max(0, x - 2)
822
+ x_right = min(img.shape[1], x + 2)
823
+ y_top = max(0, y0)
824
+ y_bottom = min(img.shape[0], y1)
825
+ img[y_top:y_bottom, x_left:x_right, :] = 0
826
+
827
+ # Small text label. PIL is used only for the label, not for the whole plot.
828
+ pil_img = Image.fromarray(img).convert("RGB")
829
+ draw = ImageDraw.Draw(pil_img)
830
+ label = "step {}/{}".format(timestep, total_steps - 1)
831
+ draw.rectangle((x0 + 4, y0 + 4, x0 + 126, y0 + 24), fill=(255, 255, 255))
832
+ draw.text((x0 + 8, y0 + 7), label, fill=(0, 0, 0))
833
+ return np.asarray(pil_img)
834
+
835
+
836
+ def _get_fast_video_writer(out_path, fps):
837
+ """Use ffmpeg's ultrafast x264 preset for interactive Spaces exports."""
838
+ return imageio.get_writer(
839
+ out_path,
840
+ fps=float(fps),
841
+ codec="libx264",
842
+ macro_block_size=16,
843
+ ffmpeg_params=[
844
+ "-preset", "ultrafast",
845
+ "-crf", "28",
846
+ "-pix_fmt", "yuv420p",
847
+ "-movflags", "+faststart",
848
+ ],
849
+ )
850
+
851
+
852
+ def build_current_trajectory_video(preset_name, custom_repo_id, custom_filename, traj_id, image_keys, display_scale, reverse_channels, fps, valid_window_len, video_stride=1):
853
  if imageio is None:
854
  return None, "Video export requires imageio and imageio-ffmpeg in requirements.txt."
855
 
 
869
  image_keys = [image_keys]
870
  image_keys_tuple = tuple(image_keys)
871
 
872
+ video_stride = int(max(1, int(video_stride)))
873
+ frame_indices = list(range(0, len(traj), video_stride))
874
+ if frame_indices and frame_indices[-1] != len(traj) - 1:
875
+ frame_indices.append(len(traj) - 1)
876
+
877
  safe_repo = re.sub(r"[^A-Za-z0-9_.-]+", "_", repo_id)
878
  safe_file = re.sub(r"[^A-Za-z0-9_.-]+", "_", filename)[-80:]
879
  out_path = os.path.join(
880
  tempfile.gettempdir(),
881
+ "trajectory_{}_{}_traj{:04d}_fps{}_stride{}.mp4".format(
882
+ safe_repo, safe_file, traj_id, int(fps), video_stride
883
+ ),
884
+ )
885
+
886
+ # Build the static status plot once. During export, only draw the cursor.
887
+ status_base, status_bounds, total_steps = get_video_status_plot_base(
888
+ repo_id,
889
+ filename,
890
+ traj_id,
891
+ int(valid_window_len),
892
  )
893
 
894
+ writer = _get_fast_video_writer(out_path, fps)
895
+ written = 0
896
  try:
897
+ for t in frame_indices:
898
+ # Use the existing cached image extraction for correctness, but avoid
899
+ # cached per-timestep status images to reduce memory pressure.
900
  gallery_items, _warnings = get_cached_gallery_items(
901
  repo_id,
902
  filename,
 
907
  bool(reverse_channels),
908
  )
909
  label = "trajectory {} | frame {}/{}".format(traj_id, t, len(traj) - 1)
910
+ status_plot = _draw_status_cursor_on_base(status_base, status_bounds, total_steps, t)
 
 
 
 
 
 
911
  frame = _compose_video_frame(gallery_items, label, status_plot=status_plot)
912
  writer.append_data(frame)
913
+ written += 1
914
  finally:
915
  writer.close()
916
 
917
+ approx_seconds = float(written) / float(max(float(fps), 1.0))
918
+ status = "Built trajectory video with optimized encoder and status rendering"
919
  status += "\nTrajectory: {}".format(traj_id)
920
+ status += "\nOriginal timesteps: {} | Written frames: {} | Stride: {}".format(len(traj), written, video_stride)
921
+ status += "\nFPS: {} | Approx video duration: {:.1f}s".format(fps, approx_seconds)
922
  status += "\nValid-window length: {}".format(int(valid_window_len))
923
+ status += "\nSpeedups: x264 ultrafast preset; static status plot rendered once; cursor drawn with NumPy/PIL"
924
  return out_path, status
925
 
 
926
  def get_available_image_keys(repo_id, filename, traj_id):
927
  n_traj = get_num_trajectories(repo_id, filename)
928
  if n_traj == 0:
 
1113
  with gr.Row():
1114
  image_keys = gr.CheckboxGroup(choices=first_keys, value=first_keys[:2], label="Image keys")
1115
  chunk_len = gr.Slider(minimum=1, maximum=64, value=DEFAULT_CHUNK_LEN, step=1, label="Valid-window length")
1116
+ display_scale = gr.State(value=DEFAULT_DISPLAY_SCALE)
1117
  reverse_channels = gr.Checkbox(value=get_default_reverse_channels(DEFAULT_PRESET), label="Reverse channels BGR↔RGB")
1118
 
1119
  with gr.Row():
 
1121
  preload_btn = gr.Button("Preload current trajectory")
1122
  video_btn = gr.Button("Build trajectory video")
1123
  video_fps = gr.Slider(minimum=1, maximum=30, value=10, step=1, label="Video FPS")
1124
+ video_stride = gr.Slider(minimum=1, maximum=10, value=1, step=1, label="Video frame stride")
1125
 
1126
  preload_status = gr.Textbox(label="Preload / video status", lines=4, value="Not preloaded yet.", interactive=False)
1127
 
 
1188
  outputs=[gallery, status_plot, info],
1189
  )
1190
 
1191
+ for widget in [image_keys, chunk_len, reverse_channels]:
1192
  widget.change(
1193
  fn=render_frame,
1194
  inputs=[preset, custom_repo_id, custom_filename, traj_slider, timestep_slider, image_keys, chunk_len, display_scale, reverse_channels],
 
1209
 
1210
  video_btn.click(
1211
  fn=build_current_trajectory_video,
1212
+ inputs=[preset, custom_repo_id, custom_filename, traj_slider, image_keys, display_scale, reverse_channels, video_fps, chunk_len, video_stride],
1213
  outputs=[trajectory_video, preload_status],
1214
  )
1215