Darius Morawiec commited on
Commit
053d37b
·
1 Parent(s): 2ac61e9

Add video of denoising steps

Browse files
Files changed (5) hide show
  1. .gitignore +4 -0
  2. app.py +138 -78
  3. pyproject.toml +2 -1
  4. requirements.txt +4 -1
  5. uv.lock +31 -13
.gitignore CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  # Created by https://www.toptal.com/developers/gitignore/api/linux,macos,dotenv,python,windows,intellij,visualstudio,visualstudiocode
2
  # Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,dotenv,python,windows,intellij,visualstudio,visualstudiocode
3
 
 
1
+ .gradio
2
+ .vscode
3
+ output
4
+
5
  # Created by https://www.toptal.com/developers/gitignore/api/linux,macos,dotenv,python,windows,intellij,visualstudio,visualstudiocode
6
  # Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,dotenv,python,windows,intellij,visualstudio,visualstudiocode
7
 
app.py CHANGED
@@ -1,13 +1,14 @@
1
- # from pathlib import Path
2
-
3
  from pathlib import Path
4
 
 
5
  import gradio as gr
6
  import PIL.Image
7
  import torch
8
  from diffusers import (
9
  DiffusionPipeline, # type: ignore
10
- QwenImageEditPlusPipeline,
11
  )
12
 
13
  # from diffusers.utils import load_image
@@ -20,49 +21,18 @@ RANK = 128
20
  TRANSFORMER_ID = f"nunchaku-tech/nunchaku-qwen-image-edit-2509/svdq-{get_precision()}_r{RANK}-qwen-image-edit-2509.safetensors"
21
  PIPELINE_ID = "Qwen/Qwen-Image-Edit-2509"
22
 
23
-
24
- def callback(
25
- pipeline: DiffusionPipeline,
26
- step: int,
27
- timestep: int,
28
- callback_kwargs: dict,
29
- ):
30
- print(f"Step {step}, Timestep {timestep}, Kwargs: {callback_kwargs.keys()}")
31
- latents = callback_kwargs.get("latents", None)
32
-
33
- height = callback_kwargs.get("height", 800)
34
- width = callback_kwargs.get("width", 512)
35
-
36
- if latents is not None:
37
- print(f"Latents shape: {latents.shape}, dtype: {latents.dtype}")
38
-
39
- latents = pipeline._unpack_latents(
40
- latents, height, width, pipeline.vae_scale_factor
41
- )
42
- latents = latents.to(pipeline.vae.dtype)
43
- latents_mean = (
44
- torch.tensor(pipeline.vae.config.latents_mean)
45
- .view(1, pipeline.vae.config.z_dim, 1, 1, 1)
46
- .to(latents.device, latents.dtype)
47
- )
48
- latents_std = 1.0 / torch.tensor(pipeline.vae.config.latents_std).view(
49
- 1, pipeline.vae.config.z_dim, 1, 1, 1
50
- ).to(latents.device, latents.dtype)
51
- latents = latents / latents_std + latents_mean
52
- image = pipeline.vae.decode(latents, return_dict=False)[0][:, :, 0]
53
- image = pipeline.image_processor.postprocess(image, output_type="pil")
54
- image = image[0]
55
-
56
- image_filename = f"step_{step:03d}_t{timestep}.png"
57
- output_dir = Path(__file__).parent / "output"
58
- output_dir.mkdir(parents=True, exist_ok=True)
59
- image.save(output_dir / image_filename)
60
-
61
- return {}
62
 
63
 
64
  class Model:
65
  def __init__(self):
 
 
 
 
66
  transformer = NunchakuQwenImageTransformer2DModel.from_pretrained(
67
  TRANSFORMER_ID
68
  )
@@ -95,7 +65,14 @@ class Model:
95
  num_inference_steps: int = 40,
96
  image_width: int = 512,
97
  image_height: int = 512,
98
- ) -> PIL.Image.Image:
 
 
 
 
 
 
 
99
  # Validate inputs
100
  if not images:
101
  raise gr.Error("No images provided. Please upload at least one image.")
@@ -116,49 +93,81 @@ class Model:
116
  width=image_width,
117
  height=image_height,
118
  generator=torch.manual_seed(0),
119
- callback_on_step_end=callback,
120
  # output_type="latent"
121
  )
122
  output = self.pipeline(**inputs)
123
  output_image = output.images[0]
124
- return output_image
125
 
 
126
 
127
- model = Model()
128
 
 
 
129
 
130
- def process_images(
131
- images,
132
- prompt,
133
- negative_prompt,
134
- true_cfg_scale,
135
- num_inference_steps,
136
- image_width,
137
- image_height,
138
- ):
139
- """Wrapper function to handle errors gracefully"""
140
 
141
- pil_images = []
 
 
 
 
 
142
 
143
- for contents in images:
144
- for content in contents:
145
- if isinstance(content, PIL.Image.Image):
146
- pil_images.append(content)
147
- break
148
 
149
- try:
150
- return model.compute(
151
- pil_images,
152
- prompt,
153
- negative_prompt,
154
- true_cfg_scale,
155
- num_inference_steps,
156
- image_width,
157
- image_height,
158
- )
159
- except Exception as e:
160
- print(f"Error processing images: {e}")
161
- raise gr.Error(f"Failed to process images: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
 
164
  with gr.Blocks() as demo:
@@ -189,6 +198,18 @@ with gr.Blocks() as demo:
189
  format="png",
190
  )
191
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  with gr.Row():
193
  with gr.Column():
194
  gr.Markdown("## Prompts")
@@ -210,8 +231,8 @@ with gr.Blocks() as demo:
210
 
211
  num_inference_steps = gr.Slider(
212
  1,
213
- 100,
214
- value=40,
215
  step=1,
216
  interactive=True,
217
  label="Number of denoising steps:",
@@ -238,6 +259,41 @@ with gr.Blocks() as demo:
238
  with gr.Row():
239
  run_button = gr.Button("Run")
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  # Connect the button to the detection function
242
  run_button.click(
243
  fn=process_images,
@@ -252,9 +308,13 @@ with gr.Blocks() as demo:
252
  ],
253
  outputs=[
254
  image_output,
 
255
  ],
256
  )
257
 
258
 
259
  if __name__ == "__main__":
260
- demo.launch()
 
 
 
 
1
+ import shutil
2
+ import time
3
  from pathlib import Path
4
 
5
+ import cv2
6
  import gradio as gr
7
  import PIL.Image
8
  import torch
9
  from diffusers import (
10
  DiffusionPipeline, # type: ignore
11
+ QwenImageEditPlusPipeline, # type: ignore
12
  )
13
 
14
  # from diffusers.utils import load_image
 
21
  TRANSFORMER_ID = f"nunchaku-tech/nunchaku-qwen-image-edit-2509/svdq-{get_precision()}_r{RANK}-qwen-image-edit-2509.safetensors"
22
  PIPELINE_ID = "Qwen/Qwen-Image-Edit-2509"
23
 
24
+ OUTPUT_DIR = Path(__file__).parent / "output"
25
+ IMAGES_DIR = OUTPUT_DIR / "images"
26
+ IMAGES_DIR.mkdir(parents=True, exist_ok=True)
27
+ VIDEO_PATH = OUTPUT_DIR / "video.mp4"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
 
30
  class Model:
31
  def __init__(self):
32
+ self.progress = gr.Progress()
33
+ self.num_inference_steps = 50
34
+ self.current_inference_step = 0
35
+
36
  transformer = NunchakuQwenImageTransformer2DModel.from_pretrained(
37
  TRANSFORMER_ID
38
  )
 
65
  num_inference_steps: int = 40,
66
  image_width: int = 512,
67
  image_height: int = 512,
68
+ ) -> tuple[PIL.Image.Image, Path]:
69
+ self.num_inference_steps = num_inference_steps
70
+ self.current_inference_step = 0
71
+ self.progress((self.current_inference_step, self.num_inference_steps))
72
+
73
+ shutil.rmtree(IMAGES_DIR, ignore_errors=True)
74
+ IMAGES_DIR.mkdir(parents=True, exist_ok=True)
75
+
76
  # Validate inputs
77
  if not images:
78
  raise gr.Error("No images provided. Please upload at least one image.")
 
93
  width=image_width,
94
  height=image_height,
95
  generator=torch.manual_seed(0),
96
+ callback_on_step_end=self.callback,
97
  # output_type="latent"
98
  )
99
  output = self.pipeline(**inputs)
100
  output_image = output.images[0]
 
101
 
102
+ # Create video from saved images
103
 
104
+ print(list(IMAGES_DIR.glob("*.png")))
105
 
106
+ # Get all PNG files and sort them
107
+ image_files = sorted(IMAGES_DIR.glob("step_*.png"))
108
 
109
+ if image_files:
110
+ # Read first image to get dimensions
111
+ first_img = cv2.imread(str(image_files[0]))
112
+ height, width, _ = first_img.shape
 
 
 
 
 
 
113
 
114
+ # Create video writer
115
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
116
+ fps = 10 # Adjust frame rate as needed
117
+ video_writer = cv2.VideoWriter(
118
+ str(VIDEO_PATH.absolute()), fourcc, fps, (width, height)
119
+ )
120
 
121
+ # Add each image to video
122
+ for img_path in image_files:
123
+ img = cv2.imread(str(img_path))
124
+ video_writer.write(img)
 
125
 
126
+ video_writer.release()
127
+ print(f"Video saved to: {VIDEO_PATH}")
128
+
129
+ time.sleep(3)
130
+
131
+ return output_image, VIDEO_PATH
132
+
133
+ def callback(
134
+ self,
135
+ pipeline: DiffusionPipeline,
136
+ step: int,
137
+ timestep: int,
138
+ callback_kwargs: dict,
139
+ ):
140
+ latents = callback_kwargs.get("latents", None)
141
+
142
+ height = callback_kwargs.get("height", 800)
143
+ width = callback_kwargs.get("width", 512)
144
+
145
+ if latents is not None:
146
+ print(f"Latents shape: {latents.shape}, dtype: {latents.dtype}")
147
+
148
+ latents = pipeline._unpack_latents(
149
+ latents, height, width, pipeline.vae_scale_factor
150
+ )
151
+ latents = latents.to(pipeline.vae.dtype)
152
+ latents_mean = (
153
+ torch.tensor(pipeline.vae.config.latents_mean)
154
+ .view(1, pipeline.vae.config.z_dim, 1, 1, 1)
155
+ .to(latents.device, latents.dtype)
156
+ )
157
+ latents_std = 1.0 / torch.tensor(pipeline.vae.config.latents_std).view(
158
+ 1, pipeline.vae.config.z_dim, 1, 1, 1
159
+ ).to(latents.device, latents.dtype)
160
+ latents = latents / latents_std + latents_mean
161
+ image = pipeline.vae.decode(latents, return_dict=False)[0][:, :, 0]
162
+ image = pipeline.image_processor.postprocess(image, output_type="pil")
163
+ image = image[0]
164
+
165
+ image.save(IMAGES_DIR / f"step_{step:03d}.png")
166
+
167
+ self.current_inference_step += 1
168
+ self.progress((self.current_inference_step, self.num_inference_steps))
169
+
170
+ return {}
171
 
172
 
173
  with gr.Blocks() as demo:
 
198
  format="png",
199
  )
200
 
201
+ with gr.Column():
202
+ gr.Markdown("## Output Video")
203
+
204
+ video_output = gr.Video(
205
+ label="Output Video",
206
+ format="mp4",
207
+ show_download_button=True,
208
+ streaming=True,
209
+ autoplay=True,
210
+ loop=False,
211
+ )
212
+
213
  with gr.Row():
214
  with gr.Column():
215
  gr.Markdown("## Prompts")
 
231
 
232
  num_inference_steps = gr.Slider(
233
  1,
234
+ 300,
235
+ value=50,
236
  step=1,
237
  interactive=True,
238
  label="Number of denoising steps:",
 
259
  with gr.Row():
260
  run_button = gr.Button("Run")
261
 
262
+ model = Model()
263
+
264
+ def process_images(
265
+ images,
266
+ prompt,
267
+ negative_prompt,
268
+ true_cfg_scale,
269
+ num_inference_steps,
270
+ image_width,
271
+ image_height,
272
+ ):
273
+ """Wrapper function to handle errors gracefully"""
274
+
275
+ pil_images = []
276
+
277
+ for contents in images:
278
+ for content in contents:
279
+ if isinstance(content, PIL.Image.Image):
280
+ pil_images.append(content)
281
+ break
282
+
283
+ try:
284
+ return model.compute(
285
+ pil_images,
286
+ prompt,
287
+ negative_prompt,
288
+ true_cfg_scale,
289
+ num_inference_steps,
290
+ image_width,
291
+ image_height,
292
+ )
293
+ except Exception as e:
294
+ print(f"Error processing images: {e}")
295
+ raise gr.Error(f"Failed to process images: {str(e)}")
296
+
297
  # Connect the button to the detection function
298
  run_button.click(
299
  fn=process_images,
 
308
  ],
309
  outputs=[
310
  image_output,
311
+ video_output,
312
  ],
313
  )
314
 
315
 
316
  if __name__ == "__main__":
317
+ demo.launch(
318
+ allowed_paths=["output/video.mp4"],
319
+ share=True,
320
+ )
pyproject.toml CHANGED
@@ -7,7 +7,8 @@ dependencies = [
7
  "nunchaku",
8
  "torch~=2.7.0",
9
  "transformers[torch]>=4.57.0",
10
- "gradio~=5.49.1"
 
11
  ]
12
 
13
  [tool.uv.sources]
 
7
  "nunchaku",
8
  "torch~=2.7.0",
9
  "transformers[torch]>=4.57.0",
10
+ "gradio~=5.49.1",
11
+ "opencv-python-headless>=4.12.0.88",
12
  ]
13
 
14
  [tool.uv.sources]
requirements.txt CHANGED
@@ -101,11 +101,12 @@ mpmath==1.3.0
101
  # via sympy
102
  networkx==3.5
103
  # via torch
104
- numpy==2.3.3
105
  # via
106
  # accelerate
107
  # diffusers
108
  # gradio
 
109
  # pandas
110
  # peft
111
  # torchvision
@@ -149,6 +150,8 @@ nvidia-nvjitlink-cu12==12.6.85
149
  # torch
150
  nvidia-nvtx-cu12==12.6.77
151
  # via torch
 
 
152
  orjson==3.11.3
153
  # via gradio
154
  packaging==25.0
 
101
  # via sympy
102
  networkx==3.5
103
  # via torch
104
+ numpy==2.2.6
105
  # via
106
  # accelerate
107
  # diffusers
108
  # gradio
109
+ # opencv-python-headless
110
  # pandas
111
  # peft
112
  # torchvision
 
150
  # torch
151
  nvidia-nvtx-cu12==12.6.77
152
  # via torch
153
+ opencv-python-headless==4.12.0.88
154
+ # via nunchaku-qwen-image-edit-2509 (pyproject.toml)
155
  orjson==3.11.3
156
  # via gradio
157
  packaging==25.0
uv.lock CHANGED
@@ -423,21 +423,20 @@ wheels = [
423
 
424
  [[package]]
425
  name = "numpy"
426
- version = "2.3.3"
427
  source = { registry = "https://pypi.org/simple" }
428
- sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" }
429
  wheels = [
430
- { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" },
431
- { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" },
432
- { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" },
433
- { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" },
434
- { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" },
435
- { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" },
436
- { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" },
437
- { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" },
438
- { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" },
439
- { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" },
440
- { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" },
441
  ]
442
 
443
  [[package]]
@@ -531,6 +530,7 @@ dependencies = [
531
  { name = "diffusers" },
532
  { name = "gradio" },
533
  { name = "nunchaku" },
 
534
  { name = "torch" },
535
  { name = "transformers", extra = ["torch"] },
536
  ]
@@ -540,6 +540,7 @@ requires-dist = [
540
  { name = "diffusers", git = "https://github.com/huggingface/diffusers?rev=a519272d97f011332588e1aaa73d32952d80f3f5" },
541
  { name = "gradio", specifier = "~=5.49.1" },
542
  { name = "nunchaku", url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1/nunchaku-1.0.1+torch2.7-cp312-cp312-linux_x86_64.whl" },
 
543
  { name = "torch", specifier = "~=2.7.0" },
544
  { name = "transformers", extras = ["torch"], specifier = ">=4.57.0" },
545
  ]
@@ -677,6 +678,23 @@ wheels = [
677
  { url = "https://files.pythonhosted.org/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265, upload-time = "2024-10-01T17:00:38.172Z" },
678
  ]
679
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  [[package]]
681
  name = "orjson"
682
  version = "3.11.3"
 
423
 
424
  [[package]]
425
  name = "numpy"
426
+ version = "2.2.6"
427
  source = { registry = "https://pypi.org/simple" }
428
+ sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
429
  wheels = [
430
+ { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
431
+ { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
432
+ { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
433
+ { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
434
+ { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
435
+ { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
436
+ { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
437
+ { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
438
+ { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
439
+ { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
 
440
  ]
441
 
442
  [[package]]
 
530
  { name = "diffusers" },
531
  { name = "gradio" },
532
  { name = "nunchaku" },
533
+ { name = "opencv-python-headless" },
534
  { name = "torch" },
535
  { name = "transformers", extra = ["torch"] },
536
  ]
 
540
  { name = "diffusers", git = "https://github.com/huggingface/diffusers?rev=a519272d97f011332588e1aaa73d32952d80f3f5" },
541
  { name = "gradio", specifier = "~=5.49.1" },
542
  { name = "nunchaku", url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1/nunchaku-1.0.1+torch2.7-cp312-cp312-linux_x86_64.whl" },
543
+ { name = "opencv-python-headless", specifier = ">=4.12.0.88" },
544
  { name = "torch", specifier = "~=2.7.0" },
545
  { name = "transformers", extras = ["torch"], specifier = ">=4.57.0" },
546
  ]
 
678
  { url = "https://files.pythonhosted.org/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265, upload-time = "2024-10-01T17:00:38.172Z" },
679
  ]
680
 
681
+ [[package]]
682
+ name = "opencv-python-headless"
683
+ version = "4.12.0.88"
684
+ source = { registry = "https://pypi.org/simple" }
685
+ dependencies = [
686
+ { name = "numpy" },
687
+ ]
688
+ sdist = { url = "https://files.pythonhosted.org/packages/a4/63/6861102ec149c3cd298f4d1ea7ce9d6adbc7529221606ff1dab991a19adb/opencv-python-headless-4.12.0.88.tar.gz", hash = "sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09", size = 95379675, upload-time = "2025-07-07T09:21:06.815Z" }
689
+ wheels = [
690
+ { url = "https://files.pythonhosted.org/packages/f7/7d/414e243c5c8216a5277afd104a319cc1291c5e23f5eeef512db5629ee7f4/opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e", size = 37877864, upload-time = "2025-07-07T09:14:41.693Z" },
691
+ { url = "https://files.pythonhosted.org/packages/05/14/7e162714beed1cd5e7b5eb66fcbcba2f065c51b1d9da2463024c84d2f7c0/opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154", size = 57326608, upload-time = "2025-07-07T09:14:51.885Z" },
692
+ { url = "https://files.pythonhosted.org/packages/69/4e/116720df7f1f7f3b59abc608ca30fbec9d2b3ae810afe4e4d26483d9dfa0/opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0", size = 33145800, upload-time = "2025-07-07T09:15:00.367Z" },
693
+ { url = "https://files.pythonhosted.org/packages/89/53/e19c21e0c4eb1275c3e2c97b081103b6dfb3938172264d283a519bf728b9/opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9", size = 54023419, upload-time = "2025-07-07T09:15:10.164Z" },
694
+ { url = "https://files.pythonhosted.org/packages/bf/9c/a76fd5414de6ec9f21f763a600058a0c3e290053cea87e0275692b1375c0/opencv_python_headless-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd", size = 30225230, upload-time = "2025-07-07T09:15:17.045Z" },
695
+ { url = "https://files.pythonhosted.org/packages/f2/35/0858e9e71b36948eafbc5e835874b63e515179dc3b742cbe3d76bc683439/opencv_python_headless-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528", size = 38923559, upload-time = "2025-07-07T09:15:25.229Z" },
696
+ ]
697
+
698
  [[package]]
699
  name = "orjson"
700
  version = "3.11.3"