- Input/output are now standard ComfyUI IMAGE tensors [B, H, W, C] float32 in [0,1], compatible with any Load/Save Video node - Remove file-path and folder_paths dependencies from the node - Add interpolate_batch_2x() to phase_interpolation.py for frame-list based processing - Output frame count: B*2-1 (original frames interleaved with synthesized midpoints) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
"""
|
|
ComfyUI custom node: Phase-Based Frame Interpolation (2x)
|
|
|
|
Input : IMAGE tensor [B, H, W, C] float32 [0, 1] — batch of video frames
|
|
Output : IMAGE tensor [B*2-1, H, W, C] float32 [0, 1] — 2x interpolated frames
|
|
|
|
Connects directly to any Load Video / Save Video node that uses the
|
|
standard ComfyUI IMAGE batch format.
|
|
"""
|
|
|
|
import numpy as np
|
|
import torch
|
|
|
|
from .phase_interpolation import interpolate_batch_2x
|
|
|
|
|
|
class PhaseBasedFrameInterpolation:
|
|
"""
|
|
2x Frame Interpolation via Phase-Based Fourier Method.
|
|
|
|
Takes a batch of video frames as a standard ComfyUI IMAGE tensor,
|
|
inserts one phase-interpolated frame between every consecutive pair,
|
|
and returns the doubled frame batch as an IMAGE tensor.
|
|
|
|
Connect: Load Video → [this node] → Save Video / Preview
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"images": (
|
|
"IMAGE",
|
|
{
|
|
"tooltip": "Batch of video frames [B, H, W, C] from a Load Video node.",
|
|
},
|
|
),
|
|
"pyramid_levels": (
|
|
"INT",
|
|
{
|
|
"default": 6,
|
|
"min": 3,
|
|
"max": 10,
|
|
"step": 1,
|
|
"tooltip": (
|
|
"Number of complex pyramid levels. "
|
|
"More levels = finer phase unwrapping but slower."
|
|
),
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("images",)
|
|
FUNCTION = "interpolate"
|
|
CATEGORY = "video/interpolation"
|
|
DESCRIPTION = (
|
|
"2x frame interpolation using Fourier-transform phase differences. "
|
|
"Accepts an IMAGE batch from a Load Video node, inserts one synthesized "
|
|
"frame between every consecutive pair, and returns the 2x frame batch. "
|
|
"Connect the output to a Save Video or Preview node."
|
|
)
|
|
|
|
def interpolate(self, images: torch.Tensor, pyramid_levels: int):
|
|
# images: [B, H, W, C] float32 in [0, 1]
|
|
B, H, W, C = images.shape
|
|
if B < 2:
|
|
raise ValueError("Need at least 2 frames for interpolation.")
|
|
|
|
# Convert tensor to list of uint8 RGB numpy arrays
|
|
np_frames = (images.cpu().numpy() * 255.0).clip(0, 255).astype(np.uint8)
|
|
frame_list = [np_frames[i] for i in range(B)]
|
|
|
|
print(f"[PhaseInterp] Input: {B} frames Output: {B * 2 - 1} frames")
|
|
|
|
# Run phase-based 2x interpolation
|
|
out_list = interpolate_batch_2x(frame_list, num_levels=pyramid_levels)
|
|
|
|
# Convert back to [B_out, H, W, C] float32 tensor in [0, 1]
|
|
out_np = np.stack(out_list, axis=0).astype(np.float32) / 255.0
|
|
out_tensor = torch.from_numpy(out_np)
|
|
|
|
return (out_tensor,)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Node registration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"PhaseBasedFrameInterpolation": PhaseBasedFrameInterpolation,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"PhaseBasedFrameInterpolation": "Phase-Based Frame Interpolation (2x)",
|
|
}
|