[Admin maintenance] Support new ZeroGPU hardware

#2
by multimodalart HF Staff - opened
Files changed (5) hide show
  1. README.md +1 -1
  2. app.py +151 -44
  3. lvdm/modules/encoders/condition.py +11 -3
  4. requirements.txt +15 -19
  5. utils/pvd_utils.py +15 -2
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 🐨
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 4.36.0
8
  app_file: app.py
9
  pinned: false
10
  license: other
 
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
10
  license: other
app.py CHANGED
@@ -1,13 +1,142 @@
1
  import os
2
- import torch
3
  import sys
4
- import spaces #fixme
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import random
7
  import gradio as gr
8
- import random
9
  from configs.infer_config import get_parser
10
- from huggingface_hub import hf_hub_download
 
 
 
 
 
 
11
 
12
  traj_examples = [
13
  ['0 -35; 0 0; 0 -0.1'],
@@ -27,6 +156,7 @@ img_examples = [
27
 
28
  max_seed = 2 ** 31
29
 
 
30
  def download_model():
31
  REPO_ID = 'Drexubery/ViewCrafter_25'
32
  filename_list = ['model.ckpt']
@@ -34,29 +164,16 @@ def download_model():
34
  local_file = os.path.join('./checkpoints/', filename)
35
  if not os.path.exists(local_file):
36
  hf_hub_download(repo_id=REPO_ID, filename=filename, local_dir='./checkpoints/', force_download=True)
37
-
38
- download_model() #fixme
39
- parser = get_parser() # infer_config.py
40
- opts = parser.parse_args() # default device: 'cuda:0'
 
41
  tmp = str(random.randint(10**(5-1), 10**5 - 1))
42
  opts.save_dir = f'./{tmp}'
43
- os.makedirs(opts.save_dir,exist_ok=True)
44
- test_tensor = torch.Tensor([0]).cuda()
45
- opts.device = str(test_tensor.device)
46
- opts.config = './configs/inference_pvd_1024_gradio.yaml' #fixme
47
- # opts.config = './configs/inference_pvd_1024_local.yaml' #fixme
48
-
49
- # install pytorch3d # fixme
50
- pyt_version_str=torch.__version__.split("+")[0].replace(".", "")
51
- version_str="".join([
52
- f"py3{sys.version_info.minor}_cu",
53
- torch.version.cuda.replace(".",""),
54
- f"_pyt{pyt_version_str}"
55
- ])
56
- print(version_str)
57
- os.system(f"{sys.executable} -m pip install --no-index --no-cache-dir pytorch3d -f https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/{version_str}/download.html")
58
- os.system("mkdir -p checkpoints/ && wget https://download.europe.naverlabs.com/ComputerVision/DUSt3R/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth -P checkpoints/")
59
- print(f'>>> System info: {version_str}')
60
 
61
 
62
  from viewcrafter import ViewCrafter
@@ -86,7 +203,7 @@ def show_traj(mode):
86
  def viewcrafter_demo(opts):
87
  css = """#input_img {max-width: 1024px !important} #output_vid {max-width: 1024px; max-height:576px} #random_button {max-width: 100px !important}"""
88
  image2video = ViewCrafter(opts, gradio = True)
89
- image2video.run_both = spaces.GPU(image2video.run_both, duration=290) # fixme
90
  with gr.Blocks(analytics_enabled=False, css=css) as viewcrafter_iface:
91
  gr.Markdown("<div align='center'> <h1> ViewCrafter: Taming Video Diffusion Models for High-fidelity Novel View Synthesis </span> </h1> \
92
  <h2 style='font-weight: 450; font-size: 1rem; margin: 0rem'>\
@@ -106,10 +223,6 @@ def viewcrafter_demo(opts):
106
 
107
  with gr.Row():
108
  with gr.Column():
109
- # # step 1: input an image
110
- # gr.Markdown("---\n## Step 1: Input an Image, selet an elevation angle and a center_scale factor", show_label=False, visible=True)
111
- # gr.Markdown("<div align='left' style='font-size:18px;color: #000000'>1. Estimate an elevation angle that represents the angle at which the image was taken; a value bigger than 0 indicates a top-down view, and it doesn't need to be precise. <br>2. The origin of the world coordinate system is by default defined at the point cloud corresponding to the center pixel of the input image. You can adjust the position of the origin by modifying center_scale; a value smaller than 1 brings the origin closer to you.</div>")
112
-
113
  with gr.Column():
114
  i2v_input_image = gr.Image(label="Input Image",elem_id="input_img")
115
  with gr.Row():
@@ -120,11 +233,11 @@ def viewcrafter_demo(opts):
120
  left = gr.Button(value = "Left")
121
  right = gr.Button(value = "Right")
122
  up = gr.Button(value = "Up")
123
- with gr.Row():
124
- down = gr.Button(value = "Down")
125
  zin = gr.Button(value = "Zoom in")
126
  zout = gr.Button(value = "Zoom out")
127
- with gr.Row():
128
  custom = gr.Button(value = "Customize")
129
  reset = gr.Button(value = "Reset")
130
 
@@ -136,25 +249,21 @@ def viewcrafter_demo(opts):
136
  gr.Markdown("<div align='left' style='font-size:18px;color: #000000'>Please refer to the <a href='https://github.com/Drexubery/ViewCrafter/blob/main/docs/gradio_tutorial.md' target='_blank'>tutorial</a> for customizing camera trajectory.</div>")
137
  gr.Examples(examples=traj_examples,
138
  inputs=[i2v_pose],
139
- )
140
 
141
 
142
- # step 3 - Generate video
143
  with gr.Column():
144
- # gr.Markdown("---\n## Step 3: Generate video", show_label=False, visible=True)
145
- # gr.Markdown("<div align='left' style='font-size:18px;color: #000000'> You can reduce the sampling steps for faster inference; try different random seed if the result is not satisfying. </div>")
146
  i2v_output_video = gr.Video(label="Generated Video",elem_id="output_vid",autoplay=True,show_share_button=True)
147
  with gr.Row():
148
  i2v_steps = gr.Slider(minimum=1, maximum=50, step=1, elem_id="i2v_steps", label="Sampling steps", value=50)
149
  i2v_seed = gr.Slider(label='Random seed', minimum=0, maximum=max_seed, step=1, value=0)
150
- i2v_end_btn = gr.Button("Generate video")
151
  i2v_traj_video = gr.Video(label="Camera Trajectory",elem_id="traj_vid",autoplay=True,show_share_button=True)
152
 
153
-
154
  gr.Examples(examples=img_examples,
155
  inputs=[i2v_input_image,i2v_elevation, i2v_center_scale,],
156
- # examples_per_page=6
157
- )
158
 
159
 
160
 
@@ -201,6 +310,4 @@ def viewcrafter_demo(opts):
201
 
202
  viewcrafter_iface = viewcrafter_demo(opts)
203
  viewcrafter_iface.queue(max_size=10)
204
- viewcrafter_iface.launch() #fixme
205
- # viewcrafter_iface.launch(server_name='11.220.92.96', server_port=80, max_threads=10,debug=True)
206
-
 
1
  import os
 
2
  import sys
3
+ import subprocess
4
+
5
+ # ---------------------------------------------------------------------------
6
+ # Blackwell ZeroGPU shim — env + heavy CUDA-extension build
7
+ # ---------------------------------------------------------------------------
8
+ # pytorch3d publishes prebuilt wheels only for torch <= 2.4. On the new
9
+ # Blackwell ZeroGPU stack (torch 2.10/2.11, CUDA 13) the old offline-install
10
+ # block in this file simply fails. Build pytorch3d from source the first time
11
+ # we get a GPU, the same way the Blackwell playbook recommends for
12
+ # nvdiffrast / diff_gaussian_rasterization.
13
+
14
+ import spaces # MUST come before torch / CUDA-touching imports
15
+ import torch
16
+
17
+ # torch.load weights_only flipped in 2.6 — old ckpts (DUSt3R/dynamicrafter) use
18
+ # argparse Namespaces / numpy scalars that the new default refuses to unpickle.
19
+ _orig_torch_load = torch.load
20
+ def _patched_torch_load(*args, **kwargs):
21
+ kwargs.setdefault("weights_only", False)
22
+ return _orig_torch_load(*args, **kwargs)
23
+ torch.load = _patched_torch_load
24
+
25
+ # xformers on the Blackwell ZeroGPU wheel ships without CUDA-built ops for
26
+ # sm_120: FA3 needs cap <= 9.0, Cutlass needs cap <= 9.0 too. Every op raises
27
+ # `NotImplementedError`. Replace `xformers.ops.memory_efficient_attention` with
28
+ # a torch-native SDPA equivalent so existing call-sites keep working.
29
+ def _mea_sdpa(q, k, v, attn_bias=None, p=0.0, scale=None, op=None):
30
+ # xformers convention: q/k/v shaped (B, M, H, K) or (B*H, M, K).
31
+ # The lvdm code path passes 3D (B*H, M, K). Convert to (B*H, 1, M, K)
32
+ # for F.scaled_dot_product_attention.
33
+ import torch.nn.functional as F
34
+ is_3d = (q.ndim == 3)
35
+ if is_3d:
36
+ q = q.unsqueeze(1); k = k.unsqueeze(1); v = v.unsqueeze(1)
37
+ elif q.ndim == 4:
38
+ # (B, M, H, K) -> (B, H, M, K)
39
+ q = q.transpose(1, 2); k = k.transpose(1, 2); v = v.transpose(1, 2)
40
+ mask = None
41
+ if attn_bias is not None and hasattr(attn_bias, "to_tensor"):
42
+ mask = attn_bias.to_tensor()
43
+ elif attn_bias is not None and torch.is_tensor(attn_bias):
44
+ mask = attn_bias
45
+ out = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=p, scale=scale)
46
+ if is_3d:
47
+ out = out.squeeze(1)
48
+ else:
49
+ out = out.transpose(1, 2)
50
+ return out
51
+
52
+ try:
53
+ import xformers.ops as _xops # noqa: E402
54
+ _xops.memory_efficient_attention = _mea_sdpa
55
+ except Exception as _e:
56
+ print(f"[xformers shim] could not patch xformers.ops: {_e}")
57
+
58
+
59
+ CUDA_HOME = "/cuda-image/usr/local/cuda-13.0"
60
+
61
+
62
+ @spaces.GPU(duration=600)
63
+ def _first_gpu_setup():
64
+ """Build pytorch3d from source against the live torch on first GPU acquire."""
65
+ try:
66
+ import pytorch3d # noqa: F401
67
+ return
68
+ except ImportError:
69
+ pass
70
+
71
+ import tempfile
72
+ patch_dir = tempfile.mkdtemp(prefix="torch_cuda_patch_")
73
+ with open(os.path.join(patch_dir, "sitecustomize.py"), "w") as f:
74
+ f.write(
75
+ "try:\n"
76
+ " import torch.utils.cpp_extension as _c\n"
77
+ " _c._check_cuda_version = lambda *a, **k: None\n"
78
+ "except Exception:\n"
79
+ " pass\n"
80
+ )
81
+
82
+ env = os.environ.copy()
83
+ env["CUDA_HOME"] = CUDA_HOME
84
+ env["CUDA_PATH"] = CUDA_HOME
85
+ env["PATH"] = os.path.join(CUDA_HOME, "bin") + os.pathsep + env.get("PATH", "")
86
+ env["PYTHONPATH"] = patch_dir + os.pathsep + env.get("PYTHONPATH", "")
87
+ env["TORCH_CUDA_ARCH_LIST"] = "12.0"
88
+ env["FORCE_CUDA"] = "1"
89
+ # CUDA 13 changed default symbol visibility; pytorch3d's pulsar renderer
90
+ # needs the old behaviour or it fails to link with "undefined reference".
91
+ # https://github.com/facebookresearch/pytorch3d/issues/2011
92
+ env["NVCC_FLAGS"] = "-static-global-template-stub=false"
93
+
94
+ subprocess.check_call(
95
+ [sys.executable, "-m", "pip", "install", "--no-deps",
96
+ "setuptools", "wheel", "ninja", "packaging"],
97
+ )
98
+ subprocess.check_call(
99
+ [sys.executable, "-m", "pip", "install",
100
+ "--no-build-isolation", "--no-deps",
101
+ "git+https://github.com/facebookresearch/pytorch3d.git@stable"],
102
+ env=env,
103
+ )
104
+
105
+
106
+ # Pre-download the DUSt3R checkpoint at module scope (CPU-only). Using
107
+ # hf_hub_download lets us avoid a fresh wget every boot.
108
+ from huggingface_hub import hf_hub_download
109
 
110
+ os.makedirs("./checkpoints/", exist_ok=True)
111
+ if not os.path.exists("./checkpoints/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth"):
112
+ try:
113
+ hf_hub_download(
114
+ repo_id="naver/DUSt3R_ViTLarge_BaseDecoder_512_dpt",
115
+ filename="DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth",
116
+ local_dir="./checkpoints/",
117
+ )
118
+ except Exception as _e:
119
+ print(f"[dust3r hf_hub_download fallback to wget] {_e}")
120
+ subprocess.check_call([
121
+ "wget", "-q", "-c",
122
+ "https://download.europe.naverlabs.com/ComputerVision/DUSt3R/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth",
123
+ "-P", "checkpoints/",
124
+ ])
125
+
126
+
127
+ # ---------------------------------------------------------------------------
128
+ # Original app
129
+ # ---------------------------------------------------------------------------
130
  import random
131
  import gradio as gr
 
132
  from configs.infer_config import get_parser
133
+
134
+
135
+ # Build pytorch3d before any module-scope code tries to import it. We need a
136
+ # GPU here because pytorch3d's CUDA kernels link against the CUDA runtime
137
+ # headers — run inside @spaces.GPU.
138
+ _first_gpu_setup()
139
+
140
 
141
  traj_examples = [
142
  ['0 -35; 0 0; 0 -0.1'],
 
156
 
157
  max_seed = 2 ** 31
158
 
159
+
160
  def download_model():
161
  REPO_ID = 'Drexubery/ViewCrafter_25'
162
  filename_list = ['model.ckpt']
 
164
  local_file = os.path.join('./checkpoints/', filename)
165
  if not os.path.exists(local_file):
166
  hf_hub_download(repo_id=REPO_ID, filename=filename, local_dir='./checkpoints/', force_download=True)
167
+
168
+
169
+ download_model()
170
+ parser = get_parser()
171
+ opts = parser.parse_args()
172
  tmp = str(random.randint(10**(5-1), 10**5 - 1))
173
  opts.save_dir = f'./{tmp}'
174
+ os.makedirs(opts.save_dir, exist_ok=True)
175
+ opts.device = "cuda" if torch.cuda.is_available() else "cpu"
176
+ opts.config = './configs/inference_pvd_1024_gradio.yaml'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
 
179
  from viewcrafter import ViewCrafter
 
203
  def viewcrafter_demo(opts):
204
  css = """#input_img {max-width: 1024px !important} #output_vid {max-width: 1024px; max-height:576px} #random_button {max-width: 100px !important}"""
205
  image2video = ViewCrafter(opts, gradio = True)
206
+ image2video.run_both = spaces.GPU(image2video.run_both, duration=290)
207
  with gr.Blocks(analytics_enabled=False, css=css) as viewcrafter_iface:
208
  gr.Markdown("<div align='center'> <h1> ViewCrafter: Taming Video Diffusion Models for High-fidelity Novel View Synthesis </span> </h1> \
209
  <h2 style='font-weight: 450; font-size: 1rem; margin: 0rem'>\
 
223
 
224
  with gr.Row():
225
  with gr.Column():
 
 
 
 
226
  with gr.Column():
227
  i2v_input_image = gr.Image(label="Input Image",elem_id="input_img")
228
  with gr.Row():
 
233
  left = gr.Button(value = "Left")
234
  right = gr.Button(value = "Right")
235
  up = gr.Button(value = "Up")
236
+ with gr.Row():
237
+ down = gr.Button(value = "Down")
238
  zin = gr.Button(value = "Zoom in")
239
  zout = gr.Button(value = "Zoom out")
240
+ with gr.Row():
241
  custom = gr.Button(value = "Customize")
242
  reset = gr.Button(value = "Reset")
243
 
 
249
  gr.Markdown("<div align='left' style='font-size:18px;color: #000000'>Please refer to the <a href='https://github.com/Drexubery/ViewCrafter/blob/main/docs/gradio_tutorial.md' target='_blank'>tutorial</a> for customizing camera trajectory.</div>")
250
  gr.Examples(examples=traj_examples,
251
  inputs=[i2v_pose],
252
+ )
253
 
254
 
 
255
  with gr.Column():
 
 
256
  i2v_output_video = gr.Video(label="Generated Video",elem_id="output_vid",autoplay=True,show_share_button=True)
257
  with gr.Row():
258
  i2v_steps = gr.Slider(minimum=1, maximum=50, step=1, elem_id="i2v_steps", label="Sampling steps", value=50)
259
  i2v_seed = gr.Slider(label='Random seed', minimum=0, maximum=max_seed, step=1, value=0)
260
+ i2v_end_btn = gr.Button("Generate video")
261
  i2v_traj_video = gr.Video(label="Camera Trajectory",elem_id="traj_vid",autoplay=True,show_share_button=True)
262
 
263
+
264
  gr.Examples(examples=img_examples,
265
  inputs=[i2v_input_image,i2v_elevation, i2v_center_scale,],
266
+ )
 
267
 
268
 
269
 
 
310
 
311
  viewcrafter_iface = viewcrafter_demo(opts)
312
  viewcrafter_iface.queue(max_size=10)
313
+ viewcrafter_iface.launch()
 
 
lvdm/modules/encoders/condition.py CHANGED
@@ -214,9 +214,16 @@ class FrozenOpenCLIPEmbedder(AbstractEncoder):
214
  def encode_with_transformer(self, text):
215
  x = self.model.token_embedding(text) # [batch_size, n_ctx, d_model]
216
  x = x + self.model.positional_embedding
217
- x = x.permute(1, 0, 2) # NLD -> LND
 
 
 
 
 
 
218
  x = self.text_transformer_forward(x, attn_mask=self.model.attn_mask)
219
- x = x.permute(1, 0, 2) # LND -> NLD
 
220
  x = self.model.ln_final(x)
221
  return x
222
 
@@ -343,7 +350,8 @@ class FrozenOpenCLIPImageEmbedderV2(AbstractEncoder):
343
  x = self.preprocess(x)
344
 
345
  # to patches - whether to use dual patchnorm - https://arxiv.org/abs/2302.01327v1
346
- if self.model.visual.input_patchnorm:
 
347
  # einops - rearrange(x, 'b c (h p1) (w p2) -> b (h w) (c p1 p2)')
348
  x = x.reshape(x.shape[0], x.shape[1], self.model.visual.grid_size[0], self.model.visual.patch_size[0], self.model.visual.grid_size[1], self.model.visual.patch_size[1])
349
  x = x.permute(0, 2, 4, 1, 3, 5)
 
214
  def encode_with_transformer(self, text):
215
  x = self.model.token_embedding(text) # [batch_size, n_ctx, d_model]
216
  x = x + self.model.positional_embedding
217
+ # open-clip-torch >= 2.20 creates nn.MultiheadAttention with batch_first=True
218
+ # by default; skip the NLD <-> LND permutes when that's the case.
219
+ batch_first = getattr(
220
+ self.model.transformer.resblocks[0].attn, "batch_first", False
221
+ )
222
+ if not batch_first:
223
+ x = x.permute(1, 0, 2) # NLD -> LND
224
  x = self.text_transformer_forward(x, attn_mask=self.model.attn_mask)
225
+ if not batch_first:
226
+ x = x.permute(1, 0, 2) # LND -> NLD
227
  x = self.model.ln_final(x)
228
  return x
229
 
 
350
  x = self.preprocess(x)
351
 
352
  # to patches - whether to use dual patchnorm - https://arxiv.org/abs/2302.01327v1
353
+ # open-clip-torch >= 2.20 removed the input_patchnorm attribute; default False.
354
+ if getattr(self.model.visual, "input_patchnorm", False):
355
  # einops - rearrange(x, 'b c (h p1) (w p2) -> b (h w) (c p1 p2)')
356
  x = x.reshape(x.shape[0], x.shape[1], self.model.visual.grid_size[0], self.model.visual.patch_size[0], self.model.visual.grid_size[1], self.model.visual.patch_size[1])
357
  x = x.permute(0, 2, 4, 1, 3, 5)
requirements.txt CHANGED
@@ -2,33 +2,29 @@ decord==0.6.0
2
  einops==0.6.1
3
  imageio==2.27.0
4
  imageio-ffmpeg==0.4.8
5
- torch==2.0.0
6
  torchvision
7
  kornia
8
  matplotlib
9
  moviepy==1.0.3
10
- numpy==1.23.5
11
- open-clip-torch==2.17.1
12
- opencv-python==4.7.0.72
13
- Pillow==9.4.0
14
- pip==23.0.1
15
- pyglet==1.5.0
16
- pytorch-lightning==1.8.3
17
- PyYAML==6.0
18
- roma==1.5.0
19
- setuptools==65.6.3
20
- scikit-image==0.20.0
21
- scikit-learn==1.2.2
22
- scipy==1.9.1
23
- timm==0.6.13
24
- tqdm==4.65.0
25
- transformers==4.25.1
26
  trimesh==4.4.3
27
  omegaconf==2.3.0
28
- triton
29
  av
30
  xformers
31
  gradio
32
  iopath
33
  fvcore
34
- # pytorch3d @ git+https://github.com/facebookresearch/pytorch3d.git
 
2
  einops==0.6.1
3
  imageio==2.27.0
4
  imageio-ffmpeg==0.4.8
 
5
  torchvision
6
  kornia
7
  matplotlib
8
  moviepy==1.0.3
9
+ numpy<2
10
+ open-clip-torch
11
+ opencv-python
12
+ Pillow
13
+ pyglet
14
+ pytorch-lightning
15
+ PyYAML
16
+ roma
17
+ scikit-image
18
+ scikit-learn
19
+ scipy
20
+ timm
21
+ tqdm
22
+ transformers
 
 
23
  trimesh==4.4.3
24
  omegaconf==2.3.0
 
25
  av
26
  xformers
27
  gradio
28
  iopath
29
  fvcore
30
+ huggingface_hub
utils/pvd_utils.py CHANGED
@@ -44,7 +44,16 @@ def save_video(data,images_path,folder=None):
44
  images = [np.array(Image.open(os.path.join(folder_name,path))) for folder_name,path in zip(folder,data)]
45
  stacked_images = np.stack(images, axis=0)
46
  tensor_data = torch.from_numpy(stacked_images).to(torch.uint8)
47
- torchvision.io.write_video(images_path, tensor_data, fps=8, video_codec='h264', options={'crf': '10'})
 
 
 
 
 
 
 
 
 
48
 
49
  def get_input_dict(img_tensor,idx,dtype = torch.float32):
50
 
@@ -510,7 +519,11 @@ def visualizer_frame(camera_poses, highlight_index):
510
  fig.canvas.draw()
511
  width, height = fig.canvas.get_width_height()
512
 
513
- img = np.frombuffer(fig.canvas.tostring_rgb(), dtype='uint8').reshape(height, width, 3)
 
 
 
 
514
  # new_width = int(width * 0.6)
515
  # start_x = (width - new_width) // 2 + new_width // 5
516
  # end_x = start_x + new_width
 
44
  images = [np.array(Image.open(os.path.join(folder_name,path))) for folder_name,path in zip(folder,data)]
45
  stacked_images = np.stack(images, axis=0)
46
  tensor_data = torch.from_numpy(stacked_images).to(torch.uint8)
47
+ # torchvision >= 0.22 removed io.write_video; fall back to imageio.
48
+ if hasattr(torchvision.io, "write_video"):
49
+ torchvision.io.write_video(images_path, tensor_data, fps=8, video_codec='h264', options={'crf': '10'})
50
+ else:
51
+ import imageio
52
+ frames_np = tensor_data.numpy() if hasattr(tensor_data, 'numpy') else np.asarray(tensor_data)
53
+ writer = imageio.get_writer(images_path, fps=8, codec='libx264', quality=8)
54
+ for frame in frames_np:
55
+ writer.append_data(frame)
56
+ writer.close()
57
 
58
  def get_input_dict(img_tensor,idx,dtype = torch.float32):
59
 
 
519
  fig.canvas.draw()
520
  width, height = fig.canvas.get_width_height()
521
 
522
+ # matplotlib >= 3.10 removed tostring_rgb(); use buffer_rgba() and drop alpha.
523
+ if hasattr(fig.canvas, "tostring_rgb"):
524
+ img = np.frombuffer(fig.canvas.tostring_rgb(), dtype='uint8').reshape(height, width, 3)
525
+ else:
526
+ img = np.asarray(fig.canvas.buffer_rgba())[..., :3].copy()
527
  # new_width = int(width * 0.6)
528
  # start_x = (width - new_width) // 2 + new_width // 5
529
  # end_x = start_x + new_width