Jack Wu commited on
Commit ·
b6ba6fc
1
Parent(s): 64a0db9
refactor: modernize type hinting by using collection ABCs and built-in generic types across the codebase
Browse files- app.py +2 -4
- pipeline/composite.py +1 -1
- pipeline/crop.py +3 -4
- pipeline/lama.py +2 -2
- pipeline/vace.py +6 -7
- pipeline/video.py +2 -2
app.py
CHANGED
|
@@ -315,9 +315,7 @@ def on_video_upload(video_path: str | None):
|
|
| 315 |
|
| 316 |
def on_preview_crop(editor_value: dict | None, meta_state: dict | None, context_px: int):
|
| 317 |
"""Compute crop region from mask and render a preview overlay."""
|
| 318 |
-
if meta_state is None:
|
| 319 |
-
return gr.update(), "Upload a video first."
|
| 320 |
-
if editor_value is None:
|
| 321 |
return gr.update(), "Upload a video first."
|
| 322 |
|
| 323 |
raw_mask = _get_mask_from_editor(editor_value)
|
|
@@ -333,7 +331,7 @@ def on_preview_crop(editor_value: dict | None, meta_state: dict | None, context_
|
|
| 333 |
if bg is None:
|
| 334 |
first_frame = np.zeros((meta.height, meta.width, 3), dtype=np.uint8)
|
| 335 |
else:
|
| 336 |
-
first_frame = np.array(Image.fromarray(np.
|
| 337 |
# Ensure first_frame is at full source resolution for the overlay
|
| 338 |
if first_frame.shape[1] != meta.width or first_frame.shape[0] != meta.height:
|
| 339 |
first_frame = np.array(
|
|
|
|
| 315 |
|
| 316 |
def on_preview_crop(editor_value: dict | None, meta_state: dict | None, context_px: int):
|
| 317 |
"""Compute crop region from mask and render a preview overlay."""
|
| 318 |
+
if meta_state is None or editor_value is None:
|
|
|
|
|
|
|
| 319 |
return gr.update(), "Upload a video first."
|
| 320 |
|
| 321 |
raw_mask = _get_mask_from_editor(editor_value)
|
|
|
|
| 331 |
if bg is None:
|
| 332 |
first_frame = np.zeros((meta.height, meta.width, 3), dtype=np.uint8)
|
| 333 |
else:
|
| 334 |
+
first_frame = np.array(Image.fromarray(np.asarray(bg)).convert("RGB"))
|
| 335 |
# Ensure first_frame is at full source resolution for the overlay
|
| 336 |
if first_frame.shape[1] != meta.width or first_frame.shape[0] != meta.height:
|
| 337 |
first_frame = np.array(
|
pipeline/composite.py
CHANGED
|
@@ -22,7 +22,7 @@ exactly once and reuses the result for every frame's ``composite_with_alpha``.
|
|
| 22 |
from __future__ import annotations
|
| 23 |
|
| 24 |
from pathlib import Path
|
| 25 |
-
from
|
| 26 |
|
| 27 |
import numpy as np
|
| 28 |
from PIL import Image
|
|
|
|
| 22 |
from __future__ import annotations
|
| 23 |
|
| 24 |
from pathlib import Path
|
| 25 |
+
from collections.abc import Iterable
|
| 26 |
|
| 27 |
import numpy as np
|
| 28 |
from PIL import Image
|
pipeline/crop.py
CHANGED
|
@@ -18,7 +18,6 @@ from __future__ import annotations
|
|
| 18 |
|
| 19 |
import math
|
| 20 |
from dataclasses import dataclass
|
| 21 |
-
from typing import Tuple
|
| 22 |
|
| 23 |
import numpy as np
|
| 24 |
from scipy.ndimage import binary_dilation
|
|
@@ -182,7 +181,7 @@ class CropRegion:
|
|
| 182 |
return self.frame_y + self.frame_h
|
| 183 |
|
| 184 |
@property
|
| 185 |
-
def pil_box(self) ->
|
| 186 |
"""(left, upper, right, lower) — 4-tuple expected by PIL.Image.crop()."""
|
| 187 |
return (self.frame_x, self.frame_y, self.frame_x2, self.frame_y2)
|
| 188 |
|
|
@@ -252,7 +251,7 @@ def mask_to_bbox(mask: np.ndarray) -> BBox:
|
|
| 252 |
def find_target_resolution(
|
| 253 |
required_w: int,
|
| 254 |
required_h: int,
|
| 255 |
-
) ->
|
| 256 |
"""
|
| 257 |
Find the smallest VACE-compatible (width, height) pair such that
|
| 258 |
width >= required_w AND height >= required_h.
|
|
@@ -489,7 +488,7 @@ def _centre_crop(
|
|
| 489 |
crop_h: int,
|
| 490 |
frame_w: int,
|
| 491 |
frame_h: int,
|
| 492 |
-
) ->
|
| 493 |
"""
|
| 494 |
Compute top-left (x, y) of a crop_w x crop_h rectangle centred at
|
| 495 |
(cx, cy), then clamp so the rectangle stays within the frame.
|
|
|
|
| 18 |
|
| 19 |
import math
|
| 20 |
from dataclasses import dataclass
|
|
|
|
| 21 |
|
| 22 |
import numpy as np
|
| 23 |
from scipy.ndimage import binary_dilation
|
|
|
|
| 181 |
return self.frame_y + self.frame_h
|
| 182 |
|
| 183 |
@property
|
| 184 |
+
def pil_box(self) -> tuple[int, int, int, int]:
|
| 185 |
"""(left, upper, right, lower) — 4-tuple expected by PIL.Image.crop()."""
|
| 186 |
return (self.frame_x, self.frame_y, self.frame_x2, self.frame_y2)
|
| 187 |
|
|
|
|
| 251 |
def find_target_resolution(
|
| 252 |
required_w: int,
|
| 253 |
required_h: int,
|
| 254 |
+
) -> tuple[int, int]:
|
| 255 |
"""
|
| 256 |
Find the smallest VACE-compatible (width, height) pair such that
|
| 257 |
width >= required_w AND height >= required_h.
|
|
|
|
| 488 |
crop_h: int,
|
| 489 |
frame_w: int,
|
| 490 |
frame_h: int,
|
| 491 |
+
) -> tuple[int, int]:
|
| 492 |
"""
|
| 493 |
Compute top-left (x, y) of a crop_w x crop_h rectangle centred at
|
| 494 |
(cx, cy), then clamp so the rectangle stays within the frame.
|
pipeline/lama.py
CHANGED
|
@@ -23,7 +23,7 @@ from __future__ import annotations
|
|
| 23 |
|
| 24 |
import os
|
| 25 |
from pathlib import Path
|
| 26 |
-
from
|
| 27 |
|
| 28 |
import numpy as np
|
| 29 |
from PIL import Image
|
|
@@ -93,7 +93,7 @@ def _get_model():
|
|
| 93 |
# ---------------------------------------------------------------------------
|
| 94 |
|
| 95 |
def inpaint_frames_lama_stream(
|
| 96 |
-
frame_paths:
|
| 97 |
crop_region: CropRegion,
|
| 98 |
inpaint_mask: np.ndarray,
|
| 99 |
progress_fn=None,
|
|
|
|
| 23 |
|
| 24 |
import os
|
| 25 |
from pathlib import Path
|
| 26 |
+
from collections.abc import Generator
|
| 27 |
|
| 28 |
import numpy as np
|
| 29 |
from PIL import Image
|
|
|
|
| 93 |
# ---------------------------------------------------------------------------
|
| 94 |
|
| 95 |
def inpaint_frames_lama_stream(
|
| 96 |
+
frame_paths: list[Path],
|
| 97 |
crop_region: CropRegion,
|
| 98 |
inpaint_mask: np.ndarray,
|
| 99 |
progress_fn=None,
|
pipeline/vace.py
CHANGED
|
@@ -61,7 +61,8 @@ from __future__ import annotations
|
|
| 61 |
import os
|
| 62 |
import threading
|
| 63 |
from pathlib import Path
|
| 64 |
-
from
|
|
|
|
| 65 |
|
| 66 |
import numpy as np
|
| 67 |
import torch
|
|
@@ -310,16 +311,16 @@ def _get_pipe():
|
|
| 310 |
# ---------------------------------------------------------------------------
|
| 311 |
|
| 312 |
def _load_chunk_crops(
|
| 313 |
-
frame_paths:
|
| 314 |
crop_region: CropRegion,
|
| 315 |
target_w: int,
|
| 316 |
target_h: int,
|
| 317 |
start: int,
|
| 318 |
end: int,
|
| 319 |
-
) ->
|
| 320 |
"""Load crops for [start, end) at VACE target resolution."""
|
| 321 |
box = crop_region.pil_box
|
| 322 |
-
crops:
|
| 323 |
for fp in frame_paths[start:end]:
|
| 324 |
img = Image.open(fp).convert("RGB")
|
| 325 |
crops.append(img.crop(box).resize((target_w, target_h), Image.LANCZOS))
|
|
@@ -331,7 +332,7 @@ def _load_chunk_crops(
|
|
| 331 |
# ---------------------------------------------------------------------------
|
| 332 |
|
| 333 |
def inpaint_frames_vace_stream(
|
| 334 |
-
frame_paths:
|
| 335 |
crop_region: CropRegion,
|
| 336 |
inpaint_mask: np.ndarray,
|
| 337 |
num_inference_steps: Optional[int] = None,
|
|
@@ -457,5 +458,3 @@ def inpaint_frames_vace_stream(
|
|
| 457 |
del result
|
| 458 |
if torch.cuda.is_available():
|
| 459 |
torch.cuda.empty_cache()
|
| 460 |
-
|
| 461 |
-
|
|
|
|
| 61 |
import os
|
| 62 |
import threading
|
| 63 |
from pathlib import Path
|
| 64 |
+
from collections.abc import Callable, Generator
|
| 65 |
+
from typing import Optional
|
| 66 |
|
| 67 |
import numpy as np
|
| 68 |
import torch
|
|
|
|
| 311 |
# ---------------------------------------------------------------------------
|
| 312 |
|
| 313 |
def _load_chunk_crops(
|
| 314 |
+
frame_paths: list[Path],
|
| 315 |
crop_region: CropRegion,
|
| 316 |
target_w: int,
|
| 317 |
target_h: int,
|
| 318 |
start: int,
|
| 319 |
end: int,
|
| 320 |
+
) -> list[Image.Image]:
|
| 321 |
"""Load crops for [start, end) at VACE target resolution."""
|
| 322 |
box = crop_region.pil_box
|
| 323 |
+
crops: list[Image.Image] = []
|
| 324 |
for fp in frame_paths[start:end]:
|
| 325 |
img = Image.open(fp).convert("RGB")
|
| 326 |
crops.append(img.crop(box).resize((target_w, target_h), Image.LANCZOS))
|
|
|
|
| 332 |
# ---------------------------------------------------------------------------
|
| 333 |
|
| 334 |
def inpaint_frames_vace_stream(
|
| 335 |
+
frame_paths: list[Path],
|
| 336 |
crop_region: CropRegion,
|
| 337 |
inpaint_mask: np.ndarray,
|
| 338 |
num_inference_steps: Optional[int] = None,
|
|
|
|
| 458 |
del result
|
| 459 |
if torch.cuda.is_available():
|
| 460 |
torch.cuda.empty_cache()
|
|
|
|
|
|
pipeline/video.py
CHANGED
|
@@ -24,7 +24,7 @@ import subprocess
|
|
| 24 |
import tempfile
|
| 25 |
from dataclasses import dataclass
|
| 26 |
from pathlib import Path
|
| 27 |
-
from typing import
|
| 28 |
|
| 29 |
import numpy as np
|
| 30 |
from PIL import Image
|
|
@@ -167,7 +167,7 @@ def extract_frames(
|
|
| 167 |
pattern: str = "%06d.png",
|
| 168 |
fps: float | None = None,
|
| 169 |
max_duration_s: float | None = None,
|
| 170 |
-
) ->
|
| 171 |
"""
|
| 172 |
Extract every frame from *video_path* as PNG images into *out_dir*.
|
| 173 |
|
|
|
|
| 24 |
import tempfile
|
| 25 |
from dataclasses import dataclass
|
| 26 |
from pathlib import Path
|
| 27 |
+
from typing import Optional
|
| 28 |
|
| 29 |
import numpy as np
|
| 30 |
from PIL import Image
|
|
|
|
| 167 |
pattern: str = "%06d.png",
|
| 168 |
fps: float | None = None,
|
| 169 |
max_duration_s: float | None = None,
|
| 170 |
+
) -> list[Path]:
|
| 171 |
"""
|
| 172 |
Extract every frame from *video_path* as PNG images into *out_dir*.
|
| 173 |
|